feat: 增强代码节点编辑器与放大阅读体验
- tinyflow-ui: 新增 CodeScriptEditor(CodeMirror 6)并支持语法高亮、上下文补全、自动括号与 _result 高亮 - tinyflow-ui: 代码节点接入引擎能力列表与节点说明提示,统一 JS/Python 编辑体验 - tinyflow-ui: 增加放大编辑模式,支持居中弹层、ESC 与点击外部关闭 - app/workflow: 对接 supportedCodeEngines 能力并透传 codeEngine provider
This commit is contained in:
5
easyflow-ui-admin/app/src/types/tinyflow-ai-vue.d.ts
vendored
Normal file
5
easyflow-ui-admin/app/src/types/tinyflow-ai-vue.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
declare module '@tinyflow-ai/vue' {
|
||||||
|
import type { DefineComponent } from 'vue';
|
||||||
|
|
||||||
|
export const Tinyflow: DefineComponent<Record<string, any>, Record<string, any>, any>;
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ onMounted(async () => {
|
|||||||
loadCustomNode(),
|
loadCustomNode(),
|
||||||
getLlmList(),
|
getLlmList(),
|
||||||
getKnowledgeList(),
|
getKnowledgeList(),
|
||||||
|
getCodeEngineList(),
|
||||||
getWorkflowInfo(workflowId.value),
|
getWorkflowInfo(workflowId.value),
|
||||||
]);
|
]);
|
||||||
showTinyFlow.value = true;
|
showTinyFlow.value = true;
|
||||||
@@ -46,6 +47,13 @@ const runParams = ref<any>(null);
|
|||||||
const tinyFlowData = ref<any>(null);
|
const tinyFlowData = ref<any>(null);
|
||||||
const llmList = ref<any>([]);
|
const llmList = ref<any>([]);
|
||||||
const knowledgeList = ref<any>([]);
|
const knowledgeList = ref<any>([]);
|
||||||
|
const codeEngineList = ref<any[]>([
|
||||||
|
{
|
||||||
|
value: 'js',
|
||||||
|
label: 'JavaScript',
|
||||||
|
available: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
const provider = computed(() => ({
|
const provider = computed(() => ({
|
||||||
llm: () => llmList.value.map((item: any) => {
|
llm: () => llmList.value.map((item: any) => {
|
||||||
let iconStr = undefined;
|
let iconStr = undefined;
|
||||||
@@ -83,6 +91,20 @@ const provider = computed(() => ({
|
|||||||
label: $t('aiWorkflow.bochaSearch'),
|
label: $t('aiWorkflow.bochaSearch'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
codeEngine: (): any => codeEngineList.value.map((item: any) => {
|
||||||
|
if (item.available === false) {
|
||||||
|
const reason = item.reason || '未满足运行条件';
|
||||||
|
return {
|
||||||
|
value: item.value,
|
||||||
|
label: `${item.label}(不可用:${reason})`,
|
||||||
|
selectable: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: item.value,
|
||||||
|
label: item.label,
|
||||||
|
};
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
const customNode = ref();
|
const customNode = ref();
|
||||||
const showTinyFlow = ref(false);
|
const showTinyFlow = ref(false);
|
||||||
@@ -166,6 +188,13 @@ async function getKnowledgeList() {
|
|||||||
knowledgeList.value = res.data;
|
knowledgeList.value = res.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async function getCodeEngineList() {
|
||||||
|
api.get('/api/v1/workflow/supportedCodeEngines').then((res) => {
|
||||||
|
if (res?.errorCode === 0 && Array.isArray(res.data) && res.data.length > 0) {
|
||||||
|
codeEngineList.value = res.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
function getRunningParams() {
|
function getRunningParams() {
|
||||||
api
|
api
|
||||||
.get(`/api/v1/workflow/getRunningParameters?id=${workflowId.value}`)
|
.get(`/api/v1/workflow/getRunningParameters?id=${workflowId.value}`)
|
||||||
|
|||||||
@@ -40,6 +40,13 @@
|
|||||||
"vite-plugin-dts": "^4.5.4"
|
"vite-plugin-dts": "^4.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.18.7",
|
||||||
|
"@codemirror/commands": "^6.8.1",
|
||||||
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
|
"@codemirror/language": "^6.11.3",
|
||||||
|
"@codemirror/state": "^6.5.2",
|
||||||
|
"@codemirror/view": "^6.38.6",
|
||||||
"@floating-ui/dom": "^1.7.4",
|
"@floating-ui/dom": "^1.7.4",
|
||||||
"@xyflow/svelte": "^1.4.2"
|
"@xyflow/svelte": "^1.4.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
key: string;
|
key: string;
|
||||||
icon?: string | Snippet;
|
icon?: string | Snippet;
|
||||||
title: string | Snippet;
|
title: string | Snippet;
|
||||||
|
titleHelp?: string;
|
||||||
description?: string | Snippet;
|
description?: string | Snippet;
|
||||||
content: string | Snippet;
|
content: string | Snippet;
|
||||||
}
|
}
|
||||||
@@ -47,6 +48,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Render target={item.title} />
|
<Render target={item.title} />
|
||||||
|
{#if item.titleHelp}
|
||||||
|
<span
|
||||||
|
class="tf-collapse-item-title-help"
|
||||||
|
data-help={item.titleHelp}
|
||||||
|
aria-label="节点使用说明"
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<span class="tf-collapse-item-title-arrow {activeKeys.includes(item.key) ? 'rotate-90' : ''}">
|
<span class="tf-collapse-item-title-arrow {activeKeys.includes(item.key) ? 'rotate-90' : ''}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path
|
||||||
@@ -75,4 +85,70 @@
|
|||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tf-collapse-item-title-help {
|
||||||
|
margin-left: 6px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #c6cfdd;
|
||||||
|
color: #64748b;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tf-collapse-item-title-help:hover {
|
||||||
|
border-color: #94a3b8;
|
||||||
|
color: #334155;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tf-collapse-item-title-help::after {
|
||||||
|
content: attr(data-help);
|
||||||
|
position: absolute;
|
||||||
|
left: calc(100% + 10px);
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
min-width: 260px;
|
||||||
|
max-width: 420px;
|
||||||
|
white-space: pre-line;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #f8fafc;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
box-shadow: 0 8px 22px rgba(2, 6, 23, 0.25);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tf-collapse-item-title-help::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: calc(100% + 4px);
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border-top: 6px solid transparent;
|
||||||
|
border-bottom: 6px solid transparent;
|
||||||
|
border-right: 6px solid #0f172a;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
z-index: 121;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tf-collapse-item-title-help:hover::after,
|
||||||
|
.tf-collapse-item-title-help:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,900 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {onMount} from 'svelte';
|
||||||
|
import {Compartment, EditorState, type Transaction} from '@codemirror/state';
|
||||||
|
import {
|
||||||
|
autocompletion,
|
||||||
|
closeBrackets,
|
||||||
|
closeBracketsKeymap,
|
||||||
|
completionKeymap,
|
||||||
|
pickedCompletion
|
||||||
|
} from '@codemirror/autocomplete';
|
||||||
|
import {defaultKeymap, history, historyKeymap, indentWithTab} from '@codemirror/commands';
|
||||||
|
import {javascript} from '@codemirror/lang-javascript';
|
||||||
|
import {defaultHighlightStyle, indentOnInput, indentUnit, syntaxHighlighting} from '@codemirror/language';
|
||||||
|
import {python} from '@codemirror/lang-python';
|
||||||
|
import {
|
||||||
|
Decoration,
|
||||||
|
type DecorationSet,
|
||||||
|
EditorView,
|
||||||
|
keymap,
|
||||||
|
placeholder as cmPlaceholder,
|
||||||
|
ViewPlugin,
|
||||||
|
type ViewUpdate
|
||||||
|
} from '@codemirror/view';
|
||||||
|
import {Button, FloatingTrigger} from '../base';
|
||||||
|
import type {Parameter} from '#types';
|
||||||
|
import {flattenParameterCandidates} from '../utils/paramToken';
|
||||||
|
import {
|
||||||
|
createBusinessCompletionSource,
|
||||||
|
type CodeEngine,
|
||||||
|
normalizeCodeEngine,
|
||||||
|
shouldAutoAppendCallParens
|
||||||
|
} from '../utils/codeCompletion';
|
||||||
|
|
||||||
|
const {
|
||||||
|
value = '',
|
||||||
|
mode = 'textarea',
|
||||||
|
engine = 'js',
|
||||||
|
parameters = [],
|
||||||
|
rows = 10,
|
||||||
|
placeholder = '',
|
||||||
|
disabled = false,
|
||||||
|
class: className = '',
|
||||||
|
style = '',
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
value?: string;
|
||||||
|
mode?: 'input' | 'textarea';
|
||||||
|
engine?: string;
|
||||||
|
parameters?: Parameter[];
|
||||||
|
rows?: number;
|
||||||
|
placeholder?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
class?: string;
|
||||||
|
style?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const TOKEN_PATTERN = /\{\{\s*([^{}]+?)\s*}}/g;
|
||||||
|
const RESULT_PATTERN = /\b_result\b/g;
|
||||||
|
const languageCompartment = new Compartment();
|
||||||
|
const indentCompartment = new Compartment();
|
||||||
|
const completionCompartment = new Compartment();
|
||||||
|
const completionSourceCompartment = new Compartment();
|
||||||
|
const decorationCompartment = new Compartment();
|
||||||
|
const editableCompartment = new Compartment();
|
||||||
|
const placeholderCompartment = new Compartment();
|
||||||
|
|
||||||
|
let containerEl = $state<HTMLDivElement | null>(null);
|
||||||
|
let editorShellEl = $state<HTMLDivElement | null>(null);
|
||||||
|
let editorView = $state<EditorView | null>(null);
|
||||||
|
let triggerObject: any;
|
||||||
|
let applyingExternalValue = $state(false);
|
||||||
|
let expanded = $state(false);
|
||||||
|
let shellOriginalParent = $state<HTMLElement | null>(null);
|
||||||
|
let shellOriginalNextSibling = $state<ChildNode | null>(null);
|
||||||
|
|
||||||
|
const isSingleLine = $derived(mode === 'input');
|
||||||
|
const normalizedEngine = $derived(normalizeCodeEngine(engine));
|
||||||
|
const minRows = $derived(isSingleLine ? 1 : Math.max(3, rows || 3));
|
||||||
|
const minHeight = $derived(`${minRows * 24}px`);
|
||||||
|
const editorMinHeight = $derived(expanded ? '100%' : minHeight);
|
||||||
|
const editorStyle = $derived(`${style};--code-editor-min-height:${editorMinHeight};`);
|
||||||
|
const paramCandidates = $derived(flattenParameterCandidates(parameters));
|
||||||
|
const hasParams = $derived(paramCandidates.length > 0);
|
||||||
|
const paramResolvedMap = $derived.by(() => {
|
||||||
|
const map = new Map<string, boolean>();
|
||||||
|
for (const candidate of paramCandidates) {
|
||||||
|
map.set(candidate.name, candidate.resolved);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
|
function emitInput(nextValue: string) {
|
||||||
|
rest.oninput?.({
|
||||||
|
target: {
|
||||||
|
value: nextValue
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitChange(nextValue: string) {
|
||||||
|
rest.onchange?.({
|
||||||
|
target: {
|
||||||
|
value: nextValue
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLanguageExtension(targetEngine: CodeEngine) {
|
||||||
|
if (targetEngine === 'python') {
|
||||||
|
return python();
|
||||||
|
}
|
||||||
|
return javascript();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIndentUnitForEngine(targetEngine: CodeEngine) {
|
||||||
|
if (targetEngine === 'python') {
|
||||||
|
return ' ';
|
||||||
|
}
|
||||||
|
return ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBusinessCompletionExtension(targetEngine: CodeEngine, candidates: typeof paramCandidates) {
|
||||||
|
return EditorState.languageData.of((_state, _pos, _side) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
autocomplete: createBusinessCompletionSource({
|
||||||
|
engine: targetEngine,
|
||||||
|
paramCandidates: candidates
|
||||||
|
})
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePickedCompletionParens(transactions: readonly Transaction[], currentState: EditorState) {
|
||||||
|
for (const tr of transactions) {
|
||||||
|
const picked = tr.annotation(pickedCompletion);
|
||||||
|
if (!picked) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursor = currentState.selection.main.head;
|
||||||
|
const nextChar = currentState.sliceDoc(cursor, cursor + 1);
|
||||||
|
if (!shouldAutoAppendCallParens(picked, nextChar)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const head = editorView.state.selection.main.head;
|
||||||
|
const afterHead = editorView.state.sliceDoc(head, head + 1);
|
||||||
|
if (afterHead === '(') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editorView.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: head,
|
||||||
|
to: head,
|
||||||
|
insert: '()'
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
anchor: head + 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHighlightDecorations(resolvedMap: Map<string, boolean>) {
|
||||||
|
const resultMark = Decoration.mark({class: 'cm-special-result'});
|
||||||
|
const validTokenMark = Decoration.mark({class: 'cm-template-token-valid'});
|
||||||
|
const unresolvedTokenMark = Decoration.mark({class: 'cm-template-token-unresolved'});
|
||||||
|
const invalidTokenMark = Decoration.mark({class: 'cm-template-token-invalid'});
|
||||||
|
|
||||||
|
function buildDecorations(source: string): DecorationSet {
|
||||||
|
const ranges: Array<{from: number; to: number; value: Decoration}> = [];
|
||||||
|
|
||||||
|
RESULT_PATTERN.lastIndex = 0;
|
||||||
|
let resultMatch: RegExpExecArray | null = RESULT_PATTERN.exec(source);
|
||||||
|
while (resultMatch) {
|
||||||
|
ranges.push({
|
||||||
|
from: resultMatch.index,
|
||||||
|
to: resultMatch.index + resultMatch[0].length,
|
||||||
|
value: resultMark
|
||||||
|
});
|
||||||
|
resultMatch = RESULT_PATTERN.exec(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
TOKEN_PATTERN.lastIndex = 0;
|
||||||
|
let tokenMatch: RegExpExecArray | null = TOKEN_PATTERN.exec(source);
|
||||||
|
while (tokenMatch) {
|
||||||
|
const tokenKey = (tokenMatch[1] || '').trim();
|
||||||
|
const resolved = resolvedMap.get(tokenKey);
|
||||||
|
const decoration =
|
||||||
|
resolved === undefined ? invalidTokenMark : resolved ? validTokenMark : unresolvedTokenMark;
|
||||||
|
ranges.push({
|
||||||
|
from: tokenMatch.index,
|
||||||
|
to: tokenMatch.index + tokenMatch[0].length,
|
||||||
|
value: decoration
|
||||||
|
});
|
||||||
|
tokenMatch = TOKEN_PATTERN.exec(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decoration.set(ranges, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ViewPlugin.fromClass(
|
||||||
|
class {
|
||||||
|
decorations: DecorationSet;
|
||||||
|
|
||||||
|
constructor(view: EditorView) {
|
||||||
|
this.decorations = buildDecorations(view.state.doc.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
update(update: ViewUpdate) {
|
||||||
|
if (update.docChanged || update.viewportChanged) {
|
||||||
|
this.decorations = buildDecorations(update.state.doc.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
decorations: (value) => value.decorations
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEditorTheme() {
|
||||||
|
return EditorView.theme({
|
||||||
|
'&': {
|
||||||
|
border: '1px solid #dcdfe6',
|
||||||
|
borderRadius: '6px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
fontSize: '13px'
|
||||||
|
},
|
||||||
|
'&.cm-focused': {
|
||||||
|
outline: 'none',
|
||||||
|
borderColor: '#4c82f7',
|
||||||
|
boxShadow: '0 0 0 1px rgba(76, 130, 247, 0.25)'
|
||||||
|
},
|
||||||
|
'&.cm-editor.cm-disabled': {
|
||||||
|
backgroundColor: '#f5f7fa',
|
||||||
|
opacity: 0.85
|
||||||
|
},
|
||||||
|
'.cm-scroller': {
|
||||||
|
minHeight: 'var(--code-editor-min-height)',
|
||||||
|
lineHeight: '1.6',
|
||||||
|
fontFamily:
|
||||||
|
"ui-monospace, SFMono-Regular, SF Mono, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace"
|
||||||
|
},
|
||||||
|
'.cm-content': {
|
||||||
|
padding: '8px 38px 8px 10px',
|
||||||
|
caretColor: '#1f2937'
|
||||||
|
},
|
||||||
|
'.cm-gutters': {
|
||||||
|
border: 'none',
|
||||||
|
background: 'transparent'
|
||||||
|
},
|
||||||
|
'.cm-activeLine': {
|
||||||
|
background: 'rgba(76, 130, 247, 0.06)'
|
||||||
|
},
|
||||||
|
'.cm-placeholder': {
|
||||||
|
color: '#c0c4cc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertTextAtSelection(text: string) {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selection = editorView.state.selection.main;
|
||||||
|
const from = selection.from;
|
||||||
|
const to = selection.to;
|
||||||
|
editorView.dispatch({
|
||||||
|
changes: {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
insert: text
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
anchor: from + text.length
|
||||||
|
}
|
||||||
|
});
|
||||||
|
editorView.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertParam(paramName: string) {
|
||||||
|
insertTextAtSelection(`{{${paramName}}}`);
|
||||||
|
emitChange(editorView?.state.doc.toString() || '');
|
||||||
|
triggerObject?.hide?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleExpanded() {
|
||||||
|
expanded = !expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeExpanded() {
|
||||||
|
expanded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveExpandedShellToBody() {
|
||||||
|
if (typeof document === 'undefined' || !editorShellEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editorShellEl.parentElement === document.body) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shellOriginalParent = editorShellEl.parentElement;
|
||||||
|
shellOriginalNextSibling = editorShellEl.nextSibling;
|
||||||
|
document.body.appendChild(editorShellEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreExpandedShell() {
|
||||||
|
if (!editorShellEl || !shellOriginalParent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editorShellEl.parentElement !== document.body) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shellOriginalNextSibling && shellOriginalNextSibling.parentNode === shellOriginalParent) {
|
||||||
|
shellOriginalParent.insertBefore(editorShellEl, shellOriginalNextSibling);
|
||||||
|
} else {
|
||||||
|
shellOriginalParent.appendChild(editorShellEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!containerEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editorView = new EditorView({
|
||||||
|
parent: containerEl,
|
||||||
|
state: EditorState.create({
|
||||||
|
doc: (value || '') as string,
|
||||||
|
extensions: [
|
||||||
|
createEditorTheme(),
|
||||||
|
syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
|
||||||
|
history(),
|
||||||
|
indentOnInput(),
|
||||||
|
closeBrackets(),
|
||||||
|
EditorView.lineWrapping,
|
||||||
|
EditorView.updateListener.of((update) => {
|
||||||
|
handlePickedCompletionParens(update.transactions, update.state);
|
||||||
|
|
||||||
|
if (!update.docChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextValue = update.state.doc.toString();
|
||||||
|
if (!applyingExternalValue) {
|
||||||
|
emitInput(nextValue);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EditorView.contentAttributes.of({spellcheck: 'false'}),
|
||||||
|
EditorView.domEventHandlers({
|
||||||
|
focus: (event) => {
|
||||||
|
rest.onfocus?.(event);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
blur: (event) => {
|
||||||
|
emitChange(editorView?.state.doc.toString() || '');
|
||||||
|
rest.onblur?.(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
keymap.of([
|
||||||
|
indentWithTab,
|
||||||
|
...defaultKeymap,
|
||||||
|
...historyKeymap,
|
||||||
|
...closeBracketsKeymap,
|
||||||
|
...completionKeymap
|
||||||
|
]),
|
||||||
|
languageCompartment.of(createLanguageExtension(normalizedEngine)),
|
||||||
|
indentCompartment.of(indentUnit.of(getIndentUnitForEngine(normalizedEngine))),
|
||||||
|
completionSourceCompartment.of(createBusinessCompletionExtension(normalizedEngine, paramCandidates)),
|
||||||
|
completionCompartment.of(
|
||||||
|
autocompletion({
|
||||||
|
activateOnTyping: true
|
||||||
|
})
|
||||||
|
),
|
||||||
|
decorationCompartment.of(createHighlightDecorations(paramResolvedMap)),
|
||||||
|
editableCompartment.of([EditorView.editable.of(!disabled), EditorState.readOnly.of(disabled)]),
|
||||||
|
placeholderCompartment.of(placeholder ? cmPlaceholder(placeholder) : [])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
editorView?.destroy();
|
||||||
|
editorView = null;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editorView.dispatch({
|
||||||
|
effects: languageCompartment.reconfigure(createLanguageExtension(normalizedEngine))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editorView.dispatch({
|
||||||
|
effects: indentCompartment.reconfigure(indentUnit.of(getIndentUnitForEngine(normalizedEngine)))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editorView.dispatch({
|
||||||
|
effects: completionSourceCompartment.reconfigure(
|
||||||
|
createBusinessCompletionExtension(normalizedEngine, paramCandidates)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editorView.dispatch({
|
||||||
|
effects: decorationCompartment.reconfigure(createHighlightDecorations(paramResolvedMap))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editorView.dispatch({
|
||||||
|
effects: editableCompartment.reconfigure([EditorView.editable.of(!disabled), EditorState.readOnly.of(disabled)])
|
||||||
|
});
|
||||||
|
|
||||||
|
const editorDom = editorView.dom;
|
||||||
|
if (disabled) {
|
||||||
|
editorDom.classList.add('cm-disabled');
|
||||||
|
} else {
|
||||||
|
editorDom.classList.remove('cm-disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editorView.dispatch({
|
||||||
|
effects: placeholderCompartment.reconfigure(placeholder ? cmPlaceholder(placeholder) : [])
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!editorView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextValue = (value || '') as string;
|
||||||
|
const currentValue = editorView.state.doc.toString();
|
||||||
|
if (nextValue === currentValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyingExternalValue = true;
|
||||||
|
editorView.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: currentValue.length,
|
||||||
|
insert: nextValue
|
||||||
|
}
|
||||||
|
});
|
||||||
|
applyingExternalValue = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!expanded || typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
closeExpanded();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('keydown', onKeydown);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', onKeydown);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const className = 'code-script-expanded-open';
|
||||||
|
if (expanded) {
|
||||||
|
document.body.classList.add(className);
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove(className);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.body.classList.remove(className);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (typeof document === 'undefined' || !editorShellEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expanded) {
|
||||||
|
moveExpandedShellToBody();
|
||||||
|
} else {
|
||||||
|
restoreExpandedShell();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
restoreExpandedShell();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="code-script-editor-shell {expanded ? 'expanded-host' : ''}" bind:this={editorShellEl}>
|
||||||
|
{#if expanded}
|
||||||
|
<button type="button" class="code-script-overlay" aria-label="关闭放大代码编辑器" onclick={closeExpanded}></button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="code-script-editor nopan nodrag {className} {expanded ? 'expanded' : ''}"
|
||||||
|
style={editorStyle}
|
||||||
|
>
|
||||||
|
<div class="code-script-editor-inner" bind:this={containerEl}></div>
|
||||||
|
|
||||||
|
<div class="code-script-action">
|
||||||
|
<div class="code-script-action-buttons">
|
||||||
|
<Button
|
||||||
|
class="code-script-button {expanded ? 'expanded-cancel' : ''}"
|
||||||
|
onclick={toggleExpanded}
|
||||||
|
title={expanded ? '取消放大' : '放大编辑区域'}
|
||||||
|
aria-label={expanded ? '取消放大' : '放大编辑区域'}
|
||||||
|
>
|
||||||
|
{#if expanded}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M21 3V9H19V6.414L15.707 9.707L14.293 8.293L17.586 5H15V3H21ZM3 21V15H5V17.586L8.293 14.293L9.707 15.707L6.414 19H9V21H3ZM9.707 8.293L8.293 9.707L5 6.414V9H3V3H9V5H6.414L9.707 8.293ZM14.293 15.707L15.707 14.293L19 17.586V15H21V21H15V19H17.586L14.293 15.707Z"></path>
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M15 3H21V9H19V5H15V3ZM3 9V3H9V5H5V9H3ZM15 21V19H19V15H21V21H15ZM3 15H5V19H9V21H3V15Z"></path>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
<FloatingTrigger placement="bottom-end" bind:this={triggerObject}>
|
||||||
|
<Button
|
||||||
|
class="code-script-button"
|
||||||
|
disabled={!hasParams || disabled}
|
||||||
|
title={hasParams ? '插入参数模板' : '请先在输入参数中定义参数'}
|
||||||
|
>
|
||||||
|
<span>{"{x}"}</span>
|
||||||
|
</Button>
|
||||||
|
{#snippet floating()}
|
||||||
|
<div class="code-script-panel nowheel">
|
||||||
|
{#if hasParams}
|
||||||
|
{#each paramCandidates as candidate}
|
||||||
|
<button
|
||||||
|
class="code-script-item {candidate.resolved ? '' : 'unresolved'}"
|
||||||
|
title={candidate.resolved ? candidate.name : `${candidate.name}(未映射)`}
|
||||||
|
onclick={() => {
|
||||||
|
insertParam(candidate.name);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{candidate.name}</span>
|
||||||
|
{#if !candidate.resolved}
|
||||||
|
<span class="code-script-item-warn" title="该参数未映射,运行时可能为空">!</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<div class="code-script-empty">请先在输入参数中定义参数</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</FloatingTrigger>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.code-script-editor-shell {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor-shell.expanded-host {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9998;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9998;
|
||||||
|
pointer-events: auto;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(15, 23, 42, 0.28);
|
||||||
|
backdrop-filter: blur(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-editor),
|
||||||
|
.code-script-editor :global(.cm-scroller),
|
||||||
|
.code-script-editor :global(.cm-content),
|
||||||
|
.code-script-editor :global(.cm-line) {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor.expanded {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 700px;
|
||||||
|
max-width: calc(100vw - 360px);
|
||||||
|
min-width: 520px;
|
||||||
|
height: min(80vh, calc(100vh - 180px));
|
||||||
|
max-height: none;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor.expanded .code-script-editor-inner {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor.expanded :global(.cm-editor) {
|
||||||
|
height: 100%;
|
||||||
|
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor.expanded :global(.cm-scroller) {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor-inner {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-action {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-action-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.code-script-button) {
|
||||||
|
height: 20px;
|
||||||
|
min-width: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #666;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.code-script-button svg) {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.code-script-button.expanded-cancel) {
|
||||||
|
height: 20px;
|
||||||
|
min-width: 20px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
border-color: #d3defd;
|
||||||
|
color: #1d4ed8;
|
||||||
|
background: #f5f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.code-script-editor.expanded {
|
||||||
|
width: calc(100vw - 120px);
|
||||||
|
max-width: none;
|
||||||
|
min-width: 0;
|
||||||
|
height: calc(100vh - 96px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.code-script-button:hover) {
|
||||||
|
background: #f0f3ff;
|
||||||
|
color: #2563eb;
|
||||||
|
border-color: #d3defd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-panel {
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 340px;
|
||||||
|
max-height: 240px;
|
||||||
|
overflow: auto;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-item {
|
||||||
|
border: none;
|
||||||
|
background: #fff;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #333;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-item:hover {
|
||||||
|
background: #f5f7ff;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-item.unresolved {
|
||||||
|
background: #fff8e6;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-item-warn {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #d97706;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-empty {
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-special-result) {
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-template-token-valid) {
|
||||||
|
color: #1e4ed8;
|
||||||
|
background: rgba(37, 99, 235, 0.12);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-template-token-unresolved) {
|
||||||
|
color: #92400e;
|
||||||
|
background: rgba(217, 119, 6, 0.16);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-template-token-invalid) {
|
||||||
|
color: #b42318;
|
||||||
|
background: rgba(217, 45, 32, 0.14);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete) {
|
||||||
|
border: 1px solid #e6eaf2;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
|
||||||
|
padding: 6px;
|
||||||
|
min-width: 260px;
|
||||||
|
max-width: 360px;
|
||||||
|
z-index: 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete > ul) {
|
||||||
|
font-family:
|
||||||
|
"ui-monospace, SFMono-Regular, SF Mono, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace";
|
||||||
|
font-size: 13px;
|
||||||
|
max-height: 240px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete > ul > li) {
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
line-height: 1.35;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]) {
|
||||||
|
background: #eef4ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete > ul > li:hover) {
|
||||||
|
background: #f6f8fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete .cm-completionIcon) {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
color: #6b7280;
|
||||||
|
opacity: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] .cm-completionIcon) {
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete .cm-completionLabel) {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete .cm-completionDetail) {
|
||||||
|
margin-left: auto;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete .cm-completionMatchedText) {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #2563eb;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar) {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar-thumb) {
|
||||||
|
background: #d6dce8;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-script-editor :global(.cm-tooltip.cm-tooltip-autocomplete > ul::-webkit-scrollbar-track) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(body.code-script-expanded-open) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
allowSettingOfCondition = true,
|
allowSettingOfCondition = true,
|
||||||
showSourceHandle = true,
|
showSourceHandle = true,
|
||||||
showTargetHandle = true,
|
showTargetHandle = true,
|
||||||
|
titleHelp = '',
|
||||||
onCollapse
|
onCollapse
|
||||||
}: {
|
}: {
|
||||||
data: NodeProps['data'],
|
data: NodeProps['data'],
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
allowSettingOfCondition?: boolean,
|
allowSettingOfCondition?: boolean,
|
||||||
showSourceHandle?: boolean,
|
showSourceHandle?: boolean,
|
||||||
showTargetHandle?: boolean,
|
showTargetHandle?: boolean,
|
||||||
|
titleHelp?: string,
|
||||||
onCollapse?: (key: string) => void,
|
onCollapse?: (key: string) => void,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@
|
|||||||
key: 'key',
|
key: 'key',
|
||||||
icon,
|
icon,
|
||||||
title: data.title as string,
|
title: data.title as string,
|
||||||
|
titleHelp,
|
||||||
description: data.description as string,
|
description: data.description as string,
|
||||||
content: children
|
content: children
|
||||||
}];
|
}];
|
||||||
@@ -296,4 +299,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -4,47 +4,78 @@
|
|||||||
import {Button, Heading, Select} from '../base';
|
import {Button, Heading, Select} from '../base';
|
||||||
import RefParameterList from '../core/RefParameterList.svelte';
|
import RefParameterList from '../core/RefParameterList.svelte';
|
||||||
import {getCurrentNodeId} from '#components/utils/NodeUtils';
|
import {getCurrentNodeId} from '#components/utils/NodeUtils';
|
||||||
|
import {getOptions} from '#components/utils/NodeUtils';
|
||||||
import {useAddParameter} from '../utils/useAddParameter.svelte';
|
import {useAddParameter} from '../utils/useAddParameter.svelte';
|
||||||
import OutputDefList from '../core/OutputDefList.svelte';
|
import OutputDefList from '../core/OutputDefList.svelte';
|
||||||
// 添加生命周期函数
|
|
||||||
import {onMount} from 'svelte';
|
import {onMount} from 'svelte';
|
||||||
import ParamTokenEditor from '../core/ParamTokenEditor.svelte';
|
import type {SelectItem} from '#types';
|
||||||
|
import CodeScriptEditor from '../core/CodeScriptEditor.svelte';
|
||||||
|
|
||||||
const { data, ...rest }: {
|
const { data, ...rest }: {
|
||||||
data: NodeProps['data'],
|
data: NodeProps['data'],
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
} = $props();
|
} = $props();
|
||||||
// 在组件挂载时检查并设置默认值
|
|
||||||
onMount(() => {
|
|
||||||
if (!data.engine) {
|
|
||||||
updateNodeData(currentNodeId, () => {
|
|
||||||
return {
|
|
||||||
engine: 'qlexpress'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const currentNodeId = getCurrentNodeId();
|
const currentNodeId = getCurrentNodeId();
|
||||||
|
const options = getOptions();
|
||||||
let currentNode = useNodesData(currentNodeId);
|
let currentNode = useNodesData(currentNodeId);
|
||||||
const { addParameter } = useAddParameter();
|
const { addParameter } = useAddParameter();
|
||||||
|
const { updateNodeData } = useSvelteFlow();
|
||||||
|
const codeNodeHelp = `结果如何返回
|
||||||
|
- 请把代码执行结果写到 _result 中。
|
||||||
|
- JS:_result.answer = 'ok'
|
||||||
|
- Python:_result['answer'] = 'ok'
|
||||||
|
|
||||||
|
输出参数如何配置
|
||||||
|
- 在“输出参数”中新增字段(如 answer、score)。
|
||||||
|
- 字段名建议与 _result 的 key 保持一致,便于下游节点引用。
|
||||||
|
- 下游节点可引用:代码节点ID.输出参数名。
|
||||||
|
|
||||||
|
示例
|
||||||
|
- 代码里写:
|
||||||
|
_result.answer = '你好';
|
||||||
|
_result.score = 95;
|
||||||
|
- 输出参数配置:answer(String)、score(Number)
|
||||||
|
- 结束节点输出参数可引用:代码节点ID.answer、代码节点ID.score`;
|
||||||
|
|
||||||
const editorParameters = $derived.by(() => {
|
const editorParameters = $derived.by(() => {
|
||||||
return (currentNode?.current?.data?.parameters as Array<any>) || data.parameters || [];
|
return (currentNode?.current?.data?.parameters as Array<any>) || data.parameters || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let engines = $state<SelectItem[]>([
|
||||||
|
{ label: 'JavaScript', value: 'js' }
|
||||||
|
]);
|
||||||
|
const defaultEngine = $derived.by(() => {
|
||||||
|
const firstAvailable = engines.find((item) => item.selectable !== false);
|
||||||
|
return firstAvailable?.value || 'js';
|
||||||
|
});
|
||||||
|
|
||||||
const { updateNodeData } = useSvelteFlow();
|
onMount(async () => {
|
||||||
|
const codeEngines = await options.provider?.codeEngine?.();
|
||||||
|
if (codeEngines && codeEngines.length > 0) {
|
||||||
|
engines = codeEngines;
|
||||||
|
}
|
||||||
|
|
||||||
const engines = [
|
const currentEngine = data.engine;
|
||||||
{ label: 'JavaScript', value: 'js' },
|
const currentEngineSupported = engines.some((item) => item.value === currentEngine && item.selectable !== false);
|
||||||
{ label: 'Groovy', value: 'groovy' },
|
if (!currentEngine || !currentEngineSupported) {
|
||||||
{ label: 'QLExpress', value: 'qlexpress' }
|
updateNodeData(currentNodeId, () => {
|
||||||
];
|
return {
|
||||||
|
engine: defaultEngine
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<NodeWrapper {data} {...rest}>
|
<NodeWrapper
|
||||||
|
{data}
|
||||||
|
titleHelp={codeNodeHelp}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
@@ -75,16 +106,17 @@
|
|||||||
engine: newValue
|
engine: newValue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}} value={data.engine ? [data.engine] : ['qlexpress']} />
|
}} value={data.engine ? [data.engine] : [defaultEngine]} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-title">执行代码</div>
|
<div class="setting-title">执行代码</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<ParamTokenEditor
|
<CodeScriptEditor
|
||||||
mode="textarea"
|
mode="textarea"
|
||||||
rows={10}
|
rows={10}
|
||||||
placeholder="请输入执行代码,注:输出内容需添加到_result中,如:_result['key'] = value 或者 _result.key = value"
|
placeholder="请输入执行代码,注:输出内容需添加到_result中,如:_result['key'] = value 或者 _result.key = value"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
engine={(data.engine as string) || (defaultEngine as string)}
|
||||||
parameters={editorParameters}
|
parameters={editorParameters}
|
||||||
oninput={(e:any)=>{
|
oninput={(e:any)=>{
|
||||||
updateNodeData(currentNodeId, ()=>{
|
updateNodeData(currentNodeId, ()=>{
|
||||||
@@ -134,4 +166,3 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createBusinessCompletions,
|
||||||
|
normalizeCodeEngine,
|
||||||
|
resolveBusinessCompletionContext,
|
||||||
|
shouldAutoAppendCallParens,
|
||||||
|
shouldSkipBusinessCompletion
|
||||||
|
} from './codeCompletion';
|
||||||
|
|
||||||
|
describe('codeCompletion utils', () => {
|
||||||
|
it('should normalize engine aliases', () => {
|
||||||
|
expect(normalizeCodeEngine('py')).toBe('python');
|
||||||
|
expect(normalizeCodeEngine('python')).toBe('python');
|
||||||
|
expect(normalizeCodeEngine('js')).toBe('javascript');
|
||||||
|
expect(normalizeCodeEngine('javascript')).toBe('javascript');
|
||||||
|
expect(normalizeCodeEngine('')).toBe('javascript');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create business completion list with stable priority and dedupe', () => {
|
||||||
|
const completions = createBusinessCompletions('python', [
|
||||||
|
{ name: 'input.text', resolved: true },
|
||||||
|
{ name: 'input.text', resolved: false },
|
||||||
|
{ name: 'question', resolved: false }
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(completions[0]).toMatchObject({
|
||||||
|
label: '_result',
|
||||||
|
type: 'variable'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(completions.filter((item) => item.label === 'input.text').length).toBe(1);
|
||||||
|
expect(completions.find((item) => item.label === 'input.text')).toMatchObject({
|
||||||
|
apply: '{{input.text}}',
|
||||||
|
detail: '参数模板'
|
||||||
|
});
|
||||||
|
expect(completions.find((item) => item.label === 'question')).toMatchObject({
|
||||||
|
apply: '{{question}}',
|
||||||
|
detail: '参数模板(未映射)'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(completions.some((item) => item.type === 'snippet' && item.label === 'if-else')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect blocked nodes for comment/string/property contexts', () => {
|
||||||
|
expect(shouldSkipBusinessCompletion('Comment')).toBe(true);
|
||||||
|
expect(shouldSkipBusinessCompletion('LineComment')).toBe(true);
|
||||||
|
expect(shouldSkipBusinessCompletion('String')).toBe(true);
|
||||||
|
expect(shouldSkipBusinessCompletion('TemplateString')).toBe(true);
|
||||||
|
expect(shouldSkipBusinessCompletion('PropertyName')).toBe(true);
|
||||||
|
expect(shouldSkipBusinessCompletion('VariableName')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve template context for parameter token completion', () => {
|
||||||
|
const source = "value = {{input.use";
|
||||||
|
const pos = source.length;
|
||||||
|
const context = resolveBusinessCompletionContext(source, pos);
|
||||||
|
expect(context).toBeTruthy();
|
||||||
|
expect(context?.from).toBe(8);
|
||||||
|
expect(context?.validFor).toBeInstanceOf(RegExp);
|
||||||
|
expect(context?.validFor.source).toBe('^[\\w.]*$');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve word context and skip property access context', () => {
|
||||||
|
const source = '_result.value';
|
||||||
|
expect(resolveBusinessCompletionContext(source, source.length)).toBeNull();
|
||||||
|
|
||||||
|
const wordSource = 'ret';
|
||||||
|
const context = resolveBusinessCompletionContext(wordSource, wordSource.length);
|
||||||
|
expect(context).toBeTruthy();
|
||||||
|
expect(context?.from).toBe(0);
|
||||||
|
expect(context?.validFor).toBeInstanceOf(RegExp);
|
||||||
|
expect(context?.validFor.source).toBe('^[\\w$]*$');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should auto append call parens for function/method completions only', () => {
|
||||||
|
expect(shouldAutoAppendCallParens({ label: 'print', type: 'function' }, '')).toBe(true);
|
||||||
|
expect(shouldAutoAppendCallParens({ label: 'run', type: 'method' }, '')).toBe(true);
|
||||||
|
expect(shouldAutoAppendCallParens({ label: 'count', type: 'variable' }, '')).toBe(false);
|
||||||
|
expect(shouldAutoAppendCallParens({ label: 'print', type: 'function' }, '(')).toBe(false);
|
||||||
|
expect(shouldAutoAppendCallParens({ label: 'call', type: 'function', apply: 'call()' }, '')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import type { Completion, CompletionResult, CompletionSource } from '@codemirror/autocomplete';
|
||||||
|
import { syntaxTree } from '@codemirror/language';
|
||||||
|
import type { ParameterCandidate } from './paramToken';
|
||||||
|
|
||||||
|
export type CodeEngine = 'javascript' | 'python';
|
||||||
|
|
||||||
|
export interface BusinessCompletionConfig {
|
||||||
|
engine: CodeEngine;
|
||||||
|
paramCandidates: ParameterCandidate[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompletionContextHint {
|
||||||
|
from: number;
|
||||||
|
validFor: RegExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLOCKED_NODE_NAMES = new Set([
|
||||||
|
'String',
|
||||||
|
'TemplateString',
|
||||||
|
'FormatString',
|
||||||
|
'Comment',
|
||||||
|
'LineComment',
|
||||||
|
'BlockComment',
|
||||||
|
'PropertyName',
|
||||||
|
'PrivatePropertyName'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const JS_SNIPPETS: Array<{ label: string; insert: string; detail: string }> = [
|
||||||
|
{
|
||||||
|
label: 'if-else',
|
||||||
|
detail: '条件分支',
|
||||||
|
insert: "if (condition) {\n _result.value = value;\n} else {\n _result.value = null;\n}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'for-of',
|
||||||
|
detail: '遍历数组',
|
||||||
|
insert: "for (const item of items) {\n // TODO\n}\n_result.done = true;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'result-object',
|
||||||
|
detail: '返回对象',
|
||||||
|
insert: "_result.message = 'ok';\n_result.data = data;"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const PYTHON_SNIPPETS: Array<{ label: string; insert: string; detail: string }> = [
|
||||||
|
{
|
||||||
|
label: 'if-else',
|
||||||
|
detail: '条件分支',
|
||||||
|
insert: "if condition:\n _result['value'] = value\nelse:\n _result['value'] = None"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'for-loop',
|
||||||
|
detail: '遍历数组',
|
||||||
|
insert: "for item in items:\n # TODO\n pass\n_result['done'] = True"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'result-object',
|
||||||
|
detail: '返回对象',
|
||||||
|
insert: "_result['message'] = 'ok'\n_result['data'] = data"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function normalizeCodeEngine(rawEngine?: string): CodeEngine {
|
||||||
|
const normalized = (rawEngine || 'js').trim().toLowerCase();
|
||||||
|
if (normalized === 'python' || normalized === 'py') {
|
||||||
|
return 'python';
|
||||||
|
}
|
||||||
|
return 'javascript';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldSkipBusinessCompletion(nodeName: string): boolean {
|
||||||
|
if (!nodeName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (BLOCKED_NODE_NAMES.has(nodeName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (nodeName.endsWith('Comment')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return nodeName.includes('String');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBusinessCompletions(
|
||||||
|
targetEngine: CodeEngine,
|
||||||
|
candidates: ParameterCandidate[]
|
||||||
|
): Completion[] {
|
||||||
|
const resultCompletion: Completion = {
|
||||||
|
label: '_result',
|
||||||
|
type: 'variable',
|
||||||
|
detail: '代码节点输出对象',
|
||||||
|
boost: 900
|
||||||
|
};
|
||||||
|
|
||||||
|
const parameterCompletions: Completion[] = candidates.map((candidate) => ({
|
||||||
|
label: candidate.name,
|
||||||
|
type: 'variable',
|
||||||
|
detail: candidate.resolved ? '参数模板' : '参数模板(未映射)',
|
||||||
|
apply: `{{${candidate.name}}}`,
|
||||||
|
boost: 700
|
||||||
|
}));
|
||||||
|
|
||||||
|
const snippetCompletions: Completion[] = (targetEngine === 'python' ? PYTHON_SNIPPETS : JS_SNIPPETS).map(
|
||||||
|
(snippet) => ({
|
||||||
|
label: snippet.label,
|
||||||
|
type: 'snippet',
|
||||||
|
detail: snippet.detail,
|
||||||
|
apply: snippet.insert,
|
||||||
|
boost: 500
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return dedupeCompletions([resultCompletion, ...parameterCompletions, ...snippetCompletions]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dedupeCompletions(items: Completion[]): Completion[] {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const deduped: Completion[] = [];
|
||||||
|
for (const item of items) {
|
||||||
|
const key = `${item.label}::${item.type || ''}`;
|
||||||
|
if (seen.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(key);
|
||||||
|
deduped.push(item);
|
||||||
|
}
|
||||||
|
return deduped;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTemplateContext(source: string, pos: number): CompletionContextHint | null {
|
||||||
|
const start = Math.max(0, pos - 300);
|
||||||
|
const prefix = source.slice(start, pos);
|
||||||
|
const match = /\{\{\s*([\w.]*)$/.exec(prefix);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
from: pos - match[0].length,
|
||||||
|
validFor: /^[\w.]*$/
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveWordContext(source: string, pos: number): CompletionContextHint | null {
|
||||||
|
let from = pos;
|
||||||
|
while (from > 0 && /[\w$]/.test(source[from - 1])) {
|
||||||
|
from -= 1;
|
||||||
|
}
|
||||||
|
if (from === pos) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from > 0 && source[from - 1] === '.') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
from,
|
||||||
|
validFor: /^[\w$]*$/
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveBusinessCompletionContext(source: string, pos: number): CompletionContextHint | null {
|
||||||
|
return resolveTemplateContext(source, pos) || resolveWordContext(source, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBusinessCompletionSource(config: BusinessCompletionConfig): CompletionSource {
|
||||||
|
return (context) => {
|
||||||
|
const node = syntaxTree(context.state).resolveInner(context.pos, -1);
|
||||||
|
if (shouldSkipBusinessCompletion(node.name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const docText = context.state.doc.toString();
|
||||||
|
const resolved = resolveBusinessCompletionContext(docText, context.pos);
|
||||||
|
if (!resolved && !context.explicit) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: CompletionResult = {
|
||||||
|
from: resolved?.from ?? context.pos,
|
||||||
|
options: createBusinessCompletions(config.engine, config.paramCandidates)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (resolved?.validFor) {
|
||||||
|
result.validFor = resolved.validFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldAutoAppendCallParens(completion: Completion | null | undefined, nextChar: string): boolean {
|
||||||
|
if (!completion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const completionType = (completion.type || '').toLowerCase();
|
||||||
|
if (completionType !== 'function' && completionType !== 'method') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextChar === '(') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof completion.apply === 'string' && completion.apply.includes('(')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof completion.apply === 'function') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -6,6 +6,13 @@ export type TinyflowData = Partial<ReturnType<ReturnType<typeof useSvelteFlow>['
|
|||||||
export type SelectItem = {
|
export type SelectItem = {
|
||||||
value: number | string;
|
value: number | string;
|
||||||
label: string | Snippet;
|
label: string | Snippet;
|
||||||
|
description?: string;
|
||||||
|
selectable?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
nodeType?: string;
|
||||||
|
dataType?: string;
|
||||||
|
displayLabel?: string;
|
||||||
|
tags?: string[];
|
||||||
children?: SelectItem[];
|
children?: SelectItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,6 +87,7 @@ export type TinyflowOptions = {
|
|||||||
llm?: () => SelectItem[] | Promise<SelectItem[]>;
|
llm?: () => SelectItem[] | Promise<SelectItem[]>;
|
||||||
knowledge?: () => SelectItem[] | Promise<SelectItem[]>;
|
knowledge?: () => SelectItem[] | Promise<SelectItem[]>;
|
||||||
searchEngine?: () => SelectItem[] | Promise<SelectItem[]>;
|
searchEngine?: () => SelectItem[] | Promise<SelectItem[]>;
|
||||||
|
codeEngine?: () => SelectItem[] | Promise<SelectItem[]>;
|
||||||
} & Record<string, () => SelectItem[] | Promise<SelectItem[]>>;
|
} & Record<string, () => SelectItem[] | Promise<SelectItem[]>>;
|
||||||
//type : node
|
//type : node
|
||||||
customNodes?: Record<string, CustomNode>;
|
customNodes?: Record<string, CustomNode>;
|
||||||
|
|||||||
160
easyflow-ui-admin/pnpm-lock.yaml
generated
160
easyflow-ui-admin/pnpm-lock.yaml
generated
@@ -1605,6 +1605,27 @@ importers:
|
|||||||
|
|
||||||
packages/tinyflow-ui:
|
packages/tinyflow-ui:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@codemirror/autocomplete':
|
||||||
|
specifier: ^6.18.7
|
||||||
|
version: 6.20.0
|
||||||
|
'@codemirror/commands':
|
||||||
|
specifier: ^6.8.1
|
||||||
|
version: 6.10.2
|
||||||
|
'@codemirror/lang-javascript':
|
||||||
|
specifier: ^6.2.4
|
||||||
|
version: 6.2.4
|
||||||
|
'@codemirror/lang-python':
|
||||||
|
specifier: ^6.2.1
|
||||||
|
version: 6.2.1
|
||||||
|
'@codemirror/language':
|
||||||
|
specifier: ^6.11.3
|
||||||
|
version: 6.12.2
|
||||||
|
'@codemirror/state':
|
||||||
|
specifier: ^6.5.2
|
||||||
|
version: 6.5.4
|
||||||
|
'@codemirror/view':
|
||||||
|
specifier: ^6.38.6
|
||||||
|
version: 6.39.15
|
||||||
'@floating-ui/dom':
|
'@floating-ui/dom':
|
||||||
specifier: ^1.7.4
|
specifier: ^1.7.4
|
||||||
version: 1.7.4
|
version: 1.7.4
|
||||||
@@ -2424,6 +2445,30 @@ packages:
|
|||||||
resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==}
|
resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@codemirror/autocomplete@6.20.0':
|
||||||
|
resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
|
||||||
|
|
||||||
|
'@codemirror/commands@6.10.2':
|
||||||
|
resolution: {integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==}
|
||||||
|
|
||||||
|
'@codemirror/lang-javascript@6.2.4':
|
||||||
|
resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==}
|
||||||
|
|
||||||
|
'@codemirror/lang-python@6.2.1':
|
||||||
|
resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==}
|
||||||
|
|
||||||
|
'@codemirror/language@6.12.2':
|
||||||
|
resolution: {integrity: sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==}
|
||||||
|
|
||||||
|
'@codemirror/lint@6.9.4':
|
||||||
|
resolution: {integrity: sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==}
|
||||||
|
|
||||||
|
'@codemirror/state@6.5.4':
|
||||||
|
resolution: {integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==}
|
||||||
|
|
||||||
|
'@codemirror/view@6.39.15':
|
||||||
|
resolution: {integrity: sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==}
|
||||||
|
|
||||||
'@commitlint/cli@19.8.1':
|
'@commitlint/cli@19.8.1':
|
||||||
resolution: {integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==}
|
resolution: {integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==}
|
||||||
engines: {node: '>=v18'}
|
engines: {node: '>=v18'}
|
||||||
@@ -3381,6 +3426,21 @@ packages:
|
|||||||
'@keyv/serialize@1.1.1':
|
'@keyv/serialize@1.1.1':
|
||||||
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
||||||
|
|
||||||
|
'@lezer/common@1.5.1':
|
||||||
|
resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==}
|
||||||
|
|
||||||
|
'@lezer/highlight@1.2.3':
|
||||||
|
resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
|
||||||
|
|
||||||
|
'@lezer/javascript@1.5.4':
|
||||||
|
resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==}
|
||||||
|
|
||||||
|
'@lezer/lr@1.4.8':
|
||||||
|
resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
|
||||||
|
|
||||||
|
'@lezer/python@1.1.18':
|
||||||
|
resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==}
|
||||||
|
|
||||||
'@manypkg/find-root@1.1.0':
|
'@manypkg/find-root@1.1.0':
|
||||||
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
|
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
|
||||||
|
|
||||||
@@ -3404,6 +3464,9 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
'@marijn/find-cluster-break@1.0.2':
|
||||||
|
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||||
|
|
||||||
'@microsoft/api-extractor-model@7.31.3':
|
'@microsoft/api-extractor-model@7.31.3':
|
||||||
resolution: {integrity: sha512-dv4quQI46p0U03TCEpasUf6JrJL3qjMN7JUAobsPElxBv4xayYYvWW9aPpfYV+Jx6hqUcVaLVOeV7+5hxsyoFQ==}
|
resolution: {integrity: sha512-dv4quQI46p0U03TCEpasUf6JrJL3qjMN7JUAobsPElxBv4xayYYvWW9aPpfYV+Jx6hqUcVaLVOeV7+5hxsyoFQ==}
|
||||||
|
|
||||||
@@ -5345,6 +5408,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
|
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
crelt@1.0.6:
|
||||||
|
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||||
|
|
||||||
croner@9.1.0:
|
croner@9.1.0:
|
||||||
resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==}
|
resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==}
|
||||||
engines: {node: '>=18.0'}
|
engines: {node: '>=18.0'}
|
||||||
@@ -9479,6 +9545,9 @@ packages:
|
|||||||
stubborn-utils@1.0.2:
|
stubborn-utils@1.0.2:
|
||||||
resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==}
|
resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==}
|
||||||
|
|
||||||
|
style-mod@4.1.3:
|
||||||
|
resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
|
||||||
|
|
||||||
style-search@0.1.0:
|
style-search@0.1.0:
|
||||||
resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
|
resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
|
||||||
|
|
||||||
@@ -10391,6 +10460,9 @@ packages:
|
|||||||
vxe-table@4.17.14:
|
vxe-table@4.17.14:
|
||||||
resolution: {integrity: sha512-bnPvt6TURzN1FMSB9q3HbyY4yiWDKXnWZW+x7x5V7S5HCbAIDgtVYMYky4JnFlsEaA7Six67Oxycktfx4/DJUQ==}
|
resolution: {integrity: sha512-bnPvt6TURzN1FMSB9q3HbyY4yiWDKXnWZW+x7x5V7S5HCbAIDgtVYMYky4JnFlsEaA7Six67Oxycktfx4/DJUQ==}
|
||||||
|
|
||||||
|
w3c-keyname@2.2.8:
|
||||||
|
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||||
|
|
||||||
watermark-js-plus@1.6.3:
|
watermark-js-plus@1.6.3:
|
||||||
resolution: {integrity: sha512-iCLOGf70KacIwjGF9MDViYxQcRiVwOH7l42qDHLeE2HeUsQD1EQuUC9cKRG/4SErTUmdqV3yf5WnKk2dRARHPQ==}
|
resolution: {integrity: sha512-iCLOGf70KacIwjGF9MDViYxQcRiVwOH7l42qDHLeE2HeUsQD1EQuUC9cKRG/4SErTUmdqV3yf5WnKk2dRARHPQ==}
|
||||||
|
|
||||||
@@ -11610,6 +11682,64 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime: 3.0.0
|
mime: 3.0.0
|
||||||
|
|
||||||
|
'@codemirror/autocomplete@6.20.0':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
|
||||||
|
'@codemirror/commands@6.10.2':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
|
||||||
|
'@codemirror/lang-javascript@6.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.20.0
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/lint': 6.9.4
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
'@lezer/javascript': 1.5.4
|
||||||
|
|
||||||
|
'@codemirror/lang-python@6.2.1':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.20.0
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
'@lezer/python': 1.1.18
|
||||||
|
|
||||||
|
'@codemirror/language@6.12.2':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
'@lezer/highlight': 1.2.3
|
||||||
|
'@lezer/lr': 1.4.8
|
||||||
|
style-mod: 4.1.3
|
||||||
|
|
||||||
|
'@codemirror/lint@6.9.4':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
crelt: 1.0.6
|
||||||
|
|
||||||
|
'@codemirror/state@6.5.4':
|
||||||
|
dependencies:
|
||||||
|
'@marijn/find-cluster-break': 1.0.2
|
||||||
|
|
||||||
|
'@codemirror/view@6.39.15':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
crelt: 1.0.6
|
||||||
|
style-mod: 4.1.3
|
||||||
|
w3c-keyname: 2.2.8
|
||||||
|
|
||||||
'@commitlint/cli@19.8.1(@types/node@24.10.1)(typescript@5.9.3)':
|
'@commitlint/cli@19.8.1(@types/node@24.10.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@commitlint/format': 19.8.1
|
'@commitlint/format': 19.8.1
|
||||||
@@ -12581,6 +12711,28 @@ snapshots:
|
|||||||
|
|
||||||
'@keyv/serialize@1.1.1': {}
|
'@keyv/serialize@1.1.1': {}
|
||||||
|
|
||||||
|
'@lezer/common@1.5.1': {}
|
||||||
|
|
||||||
|
'@lezer/highlight@1.2.3':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
|
||||||
|
'@lezer/javascript@1.5.4':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
'@lezer/highlight': 1.2.3
|
||||||
|
'@lezer/lr': 1.4.8
|
||||||
|
|
||||||
|
'@lezer/lr@1.4.8':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
|
||||||
|
'@lezer/python@1.1.18':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
'@lezer/highlight': 1.2.3
|
||||||
|
'@lezer/lr': 1.4.8
|
||||||
|
|
||||||
'@manypkg/find-root@1.1.0':
|
'@manypkg/find-root@1.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
@@ -12625,6 +12777,8 @@ snapshots:
|
|||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@marijn/find-cluster-break@1.0.2': {}
|
||||||
|
|
||||||
'@microsoft/api-extractor-model@7.31.3(@types/node@22.19.11)':
|
'@microsoft/api-extractor-model@7.31.3(@types/node@22.19.11)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@microsoft/tsdoc': 0.15.1
|
'@microsoft/tsdoc': 0.15.1
|
||||||
@@ -14791,6 +14945,8 @@ snapshots:
|
|||||||
crc-32: 1.2.2
|
crc-32: 1.2.2
|
||||||
readable-stream: 4.7.0
|
readable-stream: 4.7.0
|
||||||
|
|
||||||
|
crelt@1.0.6: {}
|
||||||
|
|
||||||
croner@9.1.0: {}
|
croner@9.1.0: {}
|
||||||
|
|
||||||
cross-env@7.0.3:
|
cross-env@7.0.3:
|
||||||
@@ -19440,6 +19596,8 @@ snapshots:
|
|||||||
|
|
||||||
stubborn-utils@1.0.2: {}
|
stubborn-utils@1.0.2: {}
|
||||||
|
|
||||||
|
style-mod@4.1.3: {}
|
||||||
|
|
||||||
style-search@0.1.0: {}
|
style-search@0.1.0: {}
|
||||||
|
|
||||||
style-value-types@5.1.2:
|
style-value-types@5.1.2:
|
||||||
@@ -20569,6 +20727,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
|
w3c-keyname@2.2.8: {}
|
||||||
|
|
||||||
watermark-js-plus@1.6.3: {}
|
watermark-js-plus@1.6.3: {}
|
||||||
|
|
||||||
web-namespaces@2.0.1: {}
|
web-namespaces@2.0.1: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user