diff --git a/easyflow-ui-admin/app/src/types/tinyflow-ai-vue.d.ts b/easyflow-ui-admin/app/src/types/tinyflow-ai-vue.d.ts new file mode 100644 index 0000000..adccf58 --- /dev/null +++ b/easyflow-ui-admin/app/src/types/tinyflow-ai-vue.d.ts @@ -0,0 +1,5 @@ +declare module '@tinyflow-ai/vue' { + import type { DefineComponent } from 'vue'; + + export const Tinyflow: DefineComponent, Record, any>; +} diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue index ae7001d..6020ee8 100644 --- a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue +++ b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue @@ -31,6 +31,7 @@ onMounted(async () => { loadCustomNode(), getLlmList(), getKnowledgeList(), + getCodeEngineList(), getWorkflowInfo(workflowId.value), ]); showTinyFlow.value = true; @@ -46,6 +47,13 @@ const runParams = ref(null); const tinyFlowData = ref(null); const llmList = ref([]); const knowledgeList = ref([]); +const codeEngineList = ref([ + { + value: 'js', + label: 'JavaScript', + available: true, + }, +]); const provider = computed(() => ({ llm: () => llmList.value.map((item: any) => { let iconStr = undefined; @@ -83,6 +91,20 @@ const provider = computed(() => ({ 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 showTinyFlow = ref(false); @@ -166,6 +188,13 @@ async function getKnowledgeList() { 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() { api .get(`/api/v1/workflow/getRunningParameters?id=${workflowId.value}`) diff --git a/easyflow-ui-admin/packages/tinyflow-ui/package.json b/easyflow-ui-admin/packages/tinyflow-ui/package.json index fbe5ca5..7d3bdd1 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/package.json +++ b/easyflow-ui-admin/packages/tinyflow-ui/package.json @@ -40,6 +40,13 @@ "vite-plugin-dts": "^4.5.4" }, "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", "@xyflow/svelte": "^1.4.2" }, diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/collapse.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/collapse.svelte index c9309bc..99fcc63 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/collapse.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/collapse.svelte @@ -6,6 +6,7 @@ key: string; icon?: string | Snippet; title: string | Snippet; + titleHelp?: string; description?: string | Snippet; content: string | Snippet; } @@ -47,6 +48,15 @@ {/if} + {#if item.titleHelp} + + ? + + {/if} diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/CodeScriptEditor.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/CodeScriptEditor.svelte new file mode 100644 index 0000000..2125688 --- /dev/null +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/CodeScriptEditor.svelte @@ -0,0 +1,900 @@ + + +
+ {#if expanded} + + {/if} + +
+
+ +
+
+ + + + {#snippet floating()} +
+ {#if hasParams} + {#each paramCandidates as candidate} + + {/each} + {:else} +
请先在输入参数中定义参数
+ {/if} +
+ {/snippet} +
+
+
+
+
+ + diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/NodeWrapper.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/NodeWrapper.svelte index babfffc..0d3790e 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/NodeWrapper.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/NodeWrapper.svelte @@ -20,6 +20,7 @@ allowSettingOfCondition = true, showSourceHandle = true, showTargetHandle = true, + titleHelp = '', onCollapse }: { data: NodeProps['data'], @@ -34,6 +35,7 @@ allowSettingOfCondition?: boolean, showSourceHandle?: boolean, showTargetHandle?: boolean, + titleHelp?: string, onCollapse?: (key: string) => void, } = $props(); @@ -45,6 +47,7 @@ key: 'key', icon, title: data.title as string, + titleHelp, description: data.description as string, content: children }]; @@ -296,4 +299,3 @@ } } - diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/CodeNode.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/CodeNode.svelte index 5e50b10..37c0de2 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/CodeNode.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/CodeNode.svelte @@ -4,47 +4,78 @@ import {Button, Heading, Select} from '../base'; import RefParameterList from '../core/RefParameterList.svelte'; import {getCurrentNodeId} from '#components/utils/NodeUtils'; + import {getOptions} from '#components/utils/NodeUtils'; import {useAddParameter} from '../utils/useAddParameter.svelte'; import OutputDefList from '../core/OutputDefList.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 }: { data: NodeProps['data'], [key: string]: any } = $props(); - // 在组件挂载时检查并设置默认值 - onMount(() => { - if (!data.engine) { - updateNodeData(currentNodeId, () => { - return { - engine: 'qlexpress' - }; - }); - } - }); + const currentNodeId = getCurrentNodeId(); + const options = getOptions(); let currentNode = useNodesData(currentNodeId); 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(() => { return (currentNode?.current?.data?.parameters as Array) || data.parameters || []; }); + let engines = $state([ + { 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 = [ - { label: 'JavaScript', value: 'js' }, - { label: 'Groovy', value: 'groovy' }, - { label: 'QLExpress', value: 'qlexpress' } - ]; + const currentEngine = data.engine; + const currentEngineSupported = engines.some((item) => item.value === currentEngine && item.selectable !== false); + if (!currentEngine || !currentEngineSupported) { + updateNodeData(currentNodeId, () => { + return { + engine: defaultEngine + }; + }); + } + }); - + {#snippet icon()} @@ -75,16 +106,17 @@ engine: newValue } }) - }} value={data.engine ? [data.engine] : ['qlexpress']} /> + }} value={data.engine ? [data.engine] : [defaultEngine]} />
执行代码
- { updateNodeData(currentNodeId, ()=>{ @@ -134,4 +166,3 @@ } - diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/utils/codeCompletion.test.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/components/utils/codeCompletion.test.ts new file mode 100644 index 0000000..f85fc92 --- /dev/null +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/utils/codeCompletion.test.ts @@ -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); + }); +}); diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/utils/codeCompletion.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/components/utils/codeCompletion.ts new file mode 100644 index 0000000..9cf5e22 --- /dev/null +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/utils/codeCompletion.ts @@ -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(); + 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; +} diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/types.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/types.ts index ae14226..0efdccb 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/types.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/types.ts @@ -6,6 +6,13 @@ export type TinyflowData = Partial[' export type SelectItem = { value: number | string; label: string | Snippet; + description?: string; + selectable?: boolean; + icon?: string; + nodeType?: string; + dataType?: string; + displayLabel?: string; + tags?: string[]; children?: SelectItem[]; }; @@ -80,6 +87,7 @@ export type TinyflowOptions = { llm?: () => SelectItem[] | Promise; knowledge?: () => SelectItem[] | Promise; searchEngine?: () => SelectItem[] | Promise; + codeEngine?: () => SelectItem[] | Promise; } & Record SelectItem[] | Promise>; //type : node customNodes?: Record; diff --git a/easyflow-ui-admin/pnpm-lock.yaml b/easyflow-ui-admin/pnpm-lock.yaml index 39ab6b0..a3876ee 100644 --- a/easyflow-ui-admin/pnpm-lock.yaml +++ b/easyflow-ui-admin/pnpm-lock.yaml @@ -1605,6 +1605,27 @@ importers: packages/tinyflow-ui: 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': specifier: ^1.7.4 version: 1.7.4 @@ -2424,6 +2445,30 @@ packages: resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} 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': resolution: {integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==} engines: {node: '>=v18'} @@ -3381,6 +3426,21 @@ packages: '@keyv/serialize@1.1.1': 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': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -3404,6 +3464,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@microsoft/api-extractor-model@7.31.3': resolution: {integrity: sha512-dv4quQI46p0U03TCEpasUf6JrJL3qjMN7JUAobsPElxBv4xayYYvWW9aPpfYV+Jx6hqUcVaLVOeV7+5hxsyoFQ==} @@ -5345,6 +5408,9 @@ packages: resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} engines: {node: '>= 14'} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + croner@9.1.0: resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==} engines: {node: '>=18.0'} @@ -9479,6 +9545,9 @@ packages: stubborn-utils@1.0.2: resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + style-search@0.1.0: resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} @@ -10391,6 +10460,9 @@ packages: vxe-table@4.17.14: resolution: {integrity: sha512-bnPvt6TURzN1FMSB9q3HbyY4yiWDKXnWZW+x7x5V7S5HCbAIDgtVYMYky4JnFlsEaA7Six67Oxycktfx4/DJUQ==} + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + watermark-js-plus@1.6.3: resolution: {integrity: sha512-iCLOGf70KacIwjGF9MDViYxQcRiVwOH7l42qDHLeE2HeUsQD1EQuUC9cKRG/4SErTUmdqV3yf5WnKk2dRARHPQ==} @@ -11610,6 +11682,64 @@ snapshots: dependencies: 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)': dependencies: '@commitlint/format': 19.8.1 @@ -12581,6 +12711,28 @@ snapshots: '@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': dependencies: '@babel/runtime': 7.28.4 @@ -12625,6 +12777,8 @@ snapshots: - encoding - supports-color + '@marijn/find-cluster-break@1.0.2': {} + '@microsoft/api-extractor-model@7.31.3(@types/node@22.19.11)': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -14791,6 +14945,8 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.7.0 + crelt@1.0.6: {} + croner@9.1.0: {} cross-env@7.0.3: @@ -19440,6 +19596,8 @@ snapshots: stubborn-utils@1.0.2: {} + style-mod@4.1.3: {} + style-search@0.1.0: {} style-value-types@5.1.2: @@ -20569,6 +20727,8 @@ snapshots: transitivePeerDependencies: - vue + w3c-keyname@2.2.8: {} + watermark-js-plus@1.6.3: {} web-namespaces@2.0.1: {}