@agentix-e/spel-editor 0.1.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +139 -0
- package/dist/index.js +49 -47
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.1] - 2026-07-14
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Remove unused imports across source files
|
|
9
|
+
- Add `.prettierrc` configuration for consistent formatting
|
|
10
|
+
- Tighten `tsconfig.json` strictness checks
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Playwright browser integration tests (`test:browser` / `test:browser:ui`)
|
|
14
|
+
- nl2spel integration tests with DeepSeek provider
|
|
15
|
+
- `DEEPSEEK_API_KEY` secret handling for integration tests
|
|
16
|
+
|
|
5
17
|
## [0.1.0] - 2026-07-12
|
|
6
18
|
|
|
7
19
|
### Added
|
|
@@ -16,4 +28,5 @@ All notable changes to this project will be documented in this file.
|
|
|
16
28
|
- `change` event with `value` and `isValid` detail
|
|
17
29
|
- Framework-agnostic — works with React, Vue, Angular, Svelte, or plain HTML
|
|
18
30
|
|
|
31
|
+
[0.1.1]: https://github.com/AgentiX-E/spel-editor/releases/tag/v0.1.1
|
|
19
32
|
[0.1.0]: https://github.com/AgentiX-E/spel-editor/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -85,6 +85,145 @@ npm install @agentix-e/spel-editor
|
|
|
85
85
|
| `--spel-border-color` | `#d0d5dd` | Editor border color |
|
|
86
86
|
| `--spel-border-radius` | `6px` | Editor border radius |
|
|
87
87
|
|
|
88
|
+
## NL Integration — Natural Language → SpEL
|
|
89
|
+
|
|
90
|
+
`@agentix-e/spel-editor` accepts `@agentix-e/nl2spel` as an **optional peer dependency**.
|
|
91
|
+
You can wire nl2spel into the editor to translate natural language input into valid SpEL expressions.
|
|
92
|
+
|
|
93
|
+
### Pattern Matching (Zero Dependencies, No API Key)
|
|
94
|
+
|
|
95
|
+
nl2spel ships with 63 built-in patterns that cover common SpEL constructs without any LLM call:
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<spel-editor id="editor" min-height="100px"></spel-editor>
|
|
99
|
+
|
|
100
|
+
<script type="module">
|
|
101
|
+
import '@agentix-e/spel-editor';
|
|
102
|
+
import { NL2SpelEngine } from '@agentix-e/nl2spel';
|
|
103
|
+
|
|
104
|
+
const editor = document.querySelector('#editor');
|
|
105
|
+
const engine = new NL2SpelEngine();
|
|
106
|
+
|
|
107
|
+
async function generateSpEL(naturalLanguage) {
|
|
108
|
+
const result = await engine.generate(naturalLanguage, { offlineOnly: true });
|
|
109
|
+
if (result.expression) {
|
|
110
|
+
editor.setValue(result.expression);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Usage:
|
|
115
|
+
await generateSpEL('amount greater than 500');
|
|
116
|
+
// Editor now shows: #amount > 500
|
|
117
|
+
</script>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### LLM-Powered: DeepSeek / OpenAI-Compatible
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm install @agentix-e/nl2spel-openai
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```html
|
|
127
|
+
<spel-editor id="editor" min-height="100px"></spel-editor>
|
|
128
|
+
|
|
129
|
+
<script type="module">
|
|
130
|
+
import '@agentix-e/spel-editor';
|
|
131
|
+
import { NL2SpelEngine } from '@agentix-e/nl2spel';
|
|
132
|
+
import { OpenAICompatibleProvider } from '@agentix-e/nl2spel-openai';
|
|
133
|
+
|
|
134
|
+
const editor = document.querySelector('#editor');
|
|
135
|
+
const engine = new NL2SpelEngine();
|
|
136
|
+
|
|
137
|
+
// Register DeepSeek as the LLM provider
|
|
138
|
+
engine.registerProvider(
|
|
139
|
+
new OpenAICompatibleProvider({
|
|
140
|
+
provider: 'deepseek',
|
|
141
|
+
apiKey: 'sk-your-deepseek-key-here',
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
async function generateSpEL(naturalLanguage, contextSchema) {
|
|
146
|
+
const result = await engine.generate(naturalLanguage, { contextSchema });
|
|
147
|
+
if (result.expression) {
|
|
148
|
+
editor.setValue(result.expression);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// With context schema for smarter completions:
|
|
153
|
+
const schema = {
|
|
154
|
+
root: {
|
|
155
|
+
name: 'order',
|
|
156
|
+
type: 'Order',
|
|
157
|
+
fields: {
|
|
158
|
+
amount: { type: 'number', description: 'Order amount' },
|
|
159
|
+
status: { type: 'string', description: 'Order status' },
|
|
160
|
+
},
|
|
161
|
+
methods: {},
|
|
162
|
+
},
|
|
163
|
+
variables: {},
|
|
164
|
+
beans: {},
|
|
165
|
+
types: {},
|
|
166
|
+
functions: {},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Wire the schema for context-aware diagnostics
|
|
170
|
+
editor.contextSchema = schema;
|
|
171
|
+
|
|
172
|
+
// Generate SpEL with full context
|
|
173
|
+
await generateSpEL('orders where amount exceeds one thousand and status is active', schema);
|
|
174
|
+
// Editor shows: #order.amount > 1000 and #order.status == 'active'
|
|
175
|
+
</script>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Browser-Local LLM: WebLLM (Gemma)
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm install @agentix-e/nl2spel-webllm @mlc-ai/web-llm
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```html
|
|
185
|
+
<spel-editor id="editor" min-height="100px"></spel-editor>
|
|
186
|
+
|
|
187
|
+
<script type="module">
|
|
188
|
+
import '@agentix-e/spel-editor';
|
|
189
|
+
import { NL2SpelEngine } from '@agentix-e/nl2spel';
|
|
190
|
+
import { WebLLMProvider } from '@agentix-e/nl2spel-webllm';
|
|
191
|
+
|
|
192
|
+
const editor = document.querySelector('#editor');
|
|
193
|
+
const engine = new NL2SpelEngine();
|
|
194
|
+
|
|
195
|
+
const provider = new WebLLMProvider({
|
|
196
|
+
modelId: 'gemma-2-2b-it', // 2B parameter model
|
|
197
|
+
maxTokens: 256,
|
|
198
|
+
temperature: 0.1,
|
|
199
|
+
debug: false, // Set true to see model logs
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
await provider.initialize();
|
|
203
|
+
engine.registerProvider(provider);
|
|
204
|
+
|
|
205
|
+
async function generateSpEL(naturalLanguage) {
|
|
206
|
+
const result = await engine.generate(naturalLanguage);
|
|
207
|
+
if (result.expression) {
|
|
208
|
+
editor.setValue(result.expression);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// First call loads the model (~1.5 GB download, cached in IndexedDB)
|
|
213
|
+
await generateSpEL('amount greater than 500');
|
|
214
|
+
// Subsequent calls are fast — model stays in memory
|
|
215
|
+
await generateSpEL('status is not null and not empty');
|
|
216
|
+
</script>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Integration Pattern Summary
|
|
220
|
+
|
|
221
|
+
| Mode | Package | API Key | Data Leaves Browser | Setup |
|
|
222
|
+
|------|---------|---------|-------------------|-------|
|
|
223
|
+
| **Pattern matching** | `@agentix-e/nl2spel` | ❌ None | ❌ No | `npm install` |
|
|
224
|
+
| **DeepSeek / OpenAI** | `+ nl2spel-openai` | ✅ Required | ✅ Yes | `npm install` + API key |
|
|
225
|
+
| **Browser-local LLM** | `+ nl2spel-webllm` | ❌ None | ❌ No | 1.5 GB model download |
|
|
226
|
+
|
|
88
227
|
## License
|
|
89
228
|
|
|
90
229
|
MIT © AgentiX-E
|
package/dist/index.js
CHANGED
|
@@ -113,64 +113,66 @@ function tokenKindToStyle(kind) {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
function createSpelStreamParser() {
|
|
116
|
+
return StreamLanguage.define(createTokenParser());
|
|
117
|
+
}
|
|
118
|
+
function createTokenParser() {
|
|
116
119
|
let _tokenizer = null;
|
|
117
120
|
let _tokens = [];
|
|
118
121
|
let _tokenIndex = 0;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
} catch {
|
|
128
|
-
_tokens = [];
|
|
129
|
-
_tokenIndex = 0;
|
|
130
|
-
stream.skipToEnd();
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
122
|
+
const startState = () => null;
|
|
123
|
+
const token = (stream) => {
|
|
124
|
+
if (!_tokenizer || _tokenIndex === 0) {
|
|
125
|
+
_tokenizer = new SpelTokenizer(stream.string);
|
|
126
|
+
let rawTokens;
|
|
127
|
+
try {
|
|
128
|
+
rawTokens = _tokenizer.tokenize();
|
|
129
|
+
} catch {
|
|
133
130
|
_tokens = [];
|
|
134
131
|
_tokenIndex = 0;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
132
|
+
stream.skipToEnd();
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
_tokens = [];
|
|
136
|
+
_tokenIndex = 0;
|
|
137
|
+
for (let i = 0; i < rawTokens.length - 1; i++) {
|
|
138
|
+
const tok = rawTokens[i];
|
|
139
|
+
const style = tokenKindToStyle(tok.kind);
|
|
140
|
+
if (tok.kind === TokenKind.IDENTIFIER) {
|
|
141
|
+
const prevTok = i > 0 ? rawTokens[i - 1] : null;
|
|
142
|
+
if (prevTok?.kind === TokenKind.HASH) {
|
|
143
|
+
_tokens.push({
|
|
144
|
+
from: tok.startPos,
|
|
145
|
+
to: tok.endPos,
|
|
146
|
+
style: "variableName"
|
|
147
|
+
});
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (prevTok?.kind === TokenKind.DOT || prevTok?.kind === TokenKind.SAFE_NAV) {
|
|
151
|
+
_tokens.push({
|
|
152
|
+
from: tok.startPos,
|
|
153
|
+
to: tok.endPos,
|
|
154
|
+
style: "propertyName"
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
156
157
|
}
|
|
157
|
-
_tokens.push({ from: tok.startPos, to: tok.endPos, style });
|
|
158
158
|
}
|
|
159
|
+
_tokens.push({ from: tok.startPos, to: tok.endPos, style });
|
|
159
160
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return t.style || null;
|
|
166
|
-
}
|
|
161
|
+
}
|
|
162
|
+
while (_tokenIndex < _tokens.length) {
|
|
163
|
+
const t = _tokens[_tokenIndex];
|
|
164
|
+
if (t.from >= stream.pos) {
|
|
165
|
+
stream.pos = t.to;
|
|
167
166
|
_tokenIndex++;
|
|
167
|
+
return t.style || null;
|
|
168
168
|
}
|
|
169
|
-
|
|
170
|
-
_tokenIndex = 0;
|
|
171
|
-
return null;
|
|
169
|
+
_tokenIndex++;
|
|
172
170
|
}
|
|
173
|
-
|
|
171
|
+
stream.skipToEnd();
|
|
172
|
+
_tokenIndex = 0;
|
|
173
|
+
return null;
|
|
174
|
+
};
|
|
175
|
+
return { startState, token };
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
// src/cm6/spel-language.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentix-e/spel-editor",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Web-embeddable Spring Expression Language (SpEL) editor — CodeMirror 6 + spel-ts powered",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
22
22
|
"test": "vitest run --coverage",
|
|
23
|
+
"test:browser": "playwright test",
|
|
24
|
+
"test:browser:ui": "playwright test --ui",
|
|
25
|
+
"test:all": "pnpm test && pnpm test:browser",
|
|
23
26
|
"test:watch": "vitest",
|
|
24
27
|
"typecheck": "tsc --noEmit",
|
|
25
28
|
"format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts'",
|
|
@@ -49,11 +52,15 @@
|
|
|
49
52
|
}
|
|
50
53
|
},
|
|
51
54
|
"devDependencies": {
|
|
55
|
+
"@agentix-e/nl2spel": "^1.1.2",
|
|
56
|
+
"@agentix-e/nl2spel-openai": "^1.1.2",
|
|
57
|
+
"@playwright/test": "^1.61.1",
|
|
52
58
|
"@vitest/coverage-v8": "^3.0.0",
|
|
53
59
|
"jsdom": "^29.1.1",
|
|
54
60
|
"prettier": "^3.9.5",
|
|
55
61
|
"tsup": "^8.0.0",
|
|
56
62
|
"typescript": "^5.8.3",
|
|
63
|
+
"vite": "^8.1.4",
|
|
57
64
|
"vitest": "^3.0.0"
|
|
58
65
|
},
|
|
59
66
|
"keywords": [
|
|
@@ -75,6 +82,9 @@
|
|
|
75
82
|
"url": "https://github.com/AgentiX-E/spel-editor/issues"
|
|
76
83
|
},
|
|
77
84
|
"homepage": "https://github.com/AgentiX-E/spel-editor#readme",
|
|
85
|
+
"pnpm": {
|
|
86
|
+
"onlyBuiltDependencies": ["esbuild"]
|
|
87
|
+
},
|
|
78
88
|
"publishConfig": {
|
|
79
89
|
"access": "public"
|
|
80
90
|
}
|