@ai-composer/core 0.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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/dist/commands/builtins.d.ts +41 -0
  4. package/dist/commands/builtins.d.ts.map +1 -0
  5. package/dist/commands/builtins.js +116 -0
  6. package/dist/commands/builtins.js.map +1 -0
  7. package/dist/commands/registry.d.ts +27 -0
  8. package/dist/commands/registry.d.ts.map +1 -0
  9. package/dist/commands/registry.js +29 -0
  10. package/dist/commands/registry.js.map +1 -0
  11. package/dist/editor.d.ts +185 -0
  12. package/dist/editor.d.ts.map +1 -0
  13. package/dist/editor.js +558 -0
  14. package/dist/editor.js.map +1 -0
  15. package/dist/errors.d.ts +15 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +22 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/events/event-bus.d.ts +19 -0
  20. package/dist/events/event-bus.d.ts.map +1 -0
  21. package/dist/events/event-bus.js +51 -0
  22. package/dist/events/event-bus.js.map +1 -0
  23. package/dist/events/events.d.ts +94 -0
  24. package/dist/events/events.d.ts.map +1 -0
  25. package/dist/events/events.js +6 -0
  26. package/dist/events/events.js.map +1 -0
  27. package/dist/factory.d.ts +17 -0
  28. package/dist/factory.d.ts.map +1 -0
  29. package/dist/factory.js +18 -0
  30. package/dist/factory.js.map +1 -0
  31. package/dist/history/history.d.ts +48 -0
  32. package/dist/history/history.d.ts.map +1 -0
  33. package/dist/history/history.js +71 -0
  34. package/dist/history/history.js.map +1 -0
  35. package/dist/index.d.ts +43 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +30 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/model/document.d.ts +54 -0
  40. package/dist/model/document.d.ts.map +1 -0
  41. package/dist/model/document.js +177 -0
  42. package/dist/model/document.js.map +1 -0
  43. package/dist/model/nodes.d.ts +80 -0
  44. package/dist/model/nodes.d.ts.map +1 -0
  45. package/dist/model/nodes.js +64 -0
  46. package/dist/model/nodes.js.map +1 -0
  47. package/dist/model/selection.d.ts +40 -0
  48. package/dist/model/selection.d.ts.map +1 -0
  49. package/dist/model/selection.js +79 -0
  50. package/dist/model/selection.js.map +1 -0
  51. package/dist/nodes/registry.d.ts +39 -0
  52. package/dist/nodes/registry.d.ts.map +1 -0
  53. package/dist/nodes/registry.js +93 -0
  54. package/dist/nodes/registry.js.map +1 -0
  55. package/dist/package.json +21 -0
  56. package/dist/plugins/registry.d.ts +53 -0
  57. package/dist/plugins/registry.d.ts.map +1 -0
  58. package/dist/plugins/registry.js +102 -0
  59. package/dist/plugins/registry.js.map +1 -0
  60. package/dist/serialization/builtins.d.ts +30 -0
  61. package/dist/serialization/builtins.d.ts.map +1 -0
  62. package/dist/serialization/builtins.js +102 -0
  63. package/dist/serialization/builtins.js.map +1 -0
  64. package/dist/serialization/index.d.ts +4 -0
  65. package/dist/serialization/index.d.ts.map +1 -0
  66. package/dist/serialization/index.js +3 -0
  67. package/dist/serialization/index.js.map +1 -0
  68. package/dist/serialization/registry.d.ts +17 -0
  69. package/dist/serialization/registry.d.ts.map +1 -0
  70. package/dist/serialization/registry.js +35 -0
  71. package/dist/serialization/registry.js.map +1 -0
  72. package/dist/serialization/serializers.d.ts +12 -0
  73. package/dist/serialization/serializers.d.ts.map +1 -0
  74. package/dist/serialization/serializers.js +3 -0
  75. package/dist/serialization/serializers.js.map +1 -0
  76. package/dist/state/state.d.ts +54 -0
  77. package/dist/state/state.d.ts.map +1 -0
  78. package/dist/state/state.js +6 -0
  79. package/dist/state/state.js.map +1 -0
  80. package/dist/triggers/engine.d.ts +102 -0
  81. package/dist/triggers/engine.d.ts.map +1 -0
  82. package/dist/triggers/engine.js +235 -0
  83. package/dist/triggers/engine.js.map +1 -0
  84. package/dist/types.d.ts +37 -0
  85. package/dist/types.d.ts.map +1 -0
  86. package/dist/types.js +3 -0
  87. package/dist/types.js.map +1 -0
  88. package/dist/utils/html.d.ts +3 -0
  89. package/dist/utils/html.d.ts.map +1 -0
  90. package/dist/utils/html.js +10 -0
  91. package/dist/utils/html.js.map +1 -0
  92. package/package.json +20 -0
  93. package/project.json +56 -0
  94. package/src/commands/builtins.ts +142 -0
  95. package/src/commands/registry.ts +53 -0
  96. package/src/commands.test.ts +118 -0
  97. package/src/editor.test.ts +311 -0
  98. package/src/editor.ts +826 -0
  99. package/src/errors.ts +25 -0
  100. package/src/events/event-bus.test.ts +62 -0
  101. package/src/events/event-bus.ts +72 -0
  102. package/src/events/events.ts +120 -0
  103. package/src/factory.ts +21 -0
  104. package/src/history/history.ts +108 -0
  105. package/src/index.ts +201 -0
  106. package/src/model/document.test.ts +130 -0
  107. package/src/model/document.ts +207 -0
  108. package/src/model/nodes.ts +151 -0
  109. package/src/model/selection.ts +107 -0
  110. package/src/nodes/registry.ts +142 -0
  111. package/src/plugins/registry.ts +163 -0
  112. package/src/serialization/builtins.ts +123 -0
  113. package/src/serialization/index.ts +20 -0
  114. package/src/serialization/registry.ts +49 -0
  115. package/src/serialization/serializers.ts +15 -0
  116. package/src/state/state.ts +57 -0
  117. package/src/triggers/engine.ts +317 -0
  118. package/src/triggers.test.ts +138 -0
  119. package/src/types.ts +42 -0
  120. package/src/utils/html.ts +9 -0
  121. package/tsconfig.json +15 -0
  122. package/tsconfig.lib.json +15 -0
  123. package/tsconfig.spec.json +16 -0
  124. package/vitest.config.ts +11 -0
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Trigger system — detects trigger characters (`@`, `/`, `#`, `$`, `:` …)
3
+ * typed before the caret, runs the owning trigger's search, and manages the
4
+ * suggestion session in editor state.
5
+ */
6
+ import { AIComposerError, toError } from '../errors';
7
+ import { createCommandNode, createCustomNode, createMentionNode, createVariableNode, } from '../model/nodes';
8
+ export function createTriggerRegistry() {
9
+ const triggers = new Map();
10
+ return {
11
+ register(trigger) {
12
+ triggers.set(trigger.id, trigger);
13
+ return () => {
14
+ if (triggers.get(trigger.id) === trigger)
15
+ triggers.delete(trigger.id);
16
+ };
17
+ },
18
+ get(id) {
19
+ return triggers.get(id);
20
+ },
21
+ list() {
22
+ return [...triggers.values()];
23
+ },
24
+ clear() {
25
+ triggers.clear();
26
+ },
27
+ };
28
+ }
29
+ /** Build the atomic node a trigger inserts for a chosen suggestion. */
30
+ export function createNodeFromSuggestion(trigger, item) {
31
+ const type = trigger.type ?? 'mention';
32
+ switch (type) {
33
+ case 'mention':
34
+ return createMentionNode({
35
+ id: item.id,
36
+ label: item.label,
37
+ ...(item.data ? { metadata: item.data } : {}),
38
+ });
39
+ case 'command':
40
+ return createCommandNode({
41
+ id: item.id,
42
+ label: item.label,
43
+ ...(item.data ? { metadata: item.data } : {}),
44
+ });
45
+ case 'variable':
46
+ return createVariableNode({ name: item.label, value: item.data?.value });
47
+ default:
48
+ return createCustomNode({
49
+ plugin: trigger.id,
50
+ nodeType: type,
51
+ data: item.data ?? { id: item.id, label: item.label },
52
+ });
53
+ }
54
+ }
55
+ const WORD_CHAR = /[\p{L}\p{N}_]/u;
56
+ export class TriggerEngine {
57
+ host;
58
+ active = null;
59
+ items = [];
60
+ activeIndex = -1;
61
+ searchToken = 0;
62
+ constructor(host) {
63
+ this.host = host;
64
+ }
65
+ get activeTrigger() {
66
+ return this.active;
67
+ }
68
+ get suggestions() {
69
+ return this.items;
70
+ }
71
+ get activeSuggestionIndex() {
72
+ return this.activeIndex;
73
+ }
74
+ /** Re-evaluate the trigger context after a document change. */
75
+ handleDocumentChange() {
76
+ this.evaluate();
77
+ }
78
+ handleSelectionChange() {
79
+ this.evaluate();
80
+ }
81
+ /** Move the highlighted suggestion (ArrowUp/ArrowDown). */
82
+ moveSelection(delta) {
83
+ if (!this.active || this.items.length === 0)
84
+ return;
85
+ const next = (this.activeIndex + delta + this.items.length) % this.items.length;
86
+ this.setSuggestions(this.active.triggerId, this.items, next);
87
+ }
88
+ /** Accept a suggestion (click, Enter/Tab, or programmatic). */
89
+ async accept(item) {
90
+ const state = this.active;
91
+ if (!state)
92
+ return;
93
+ const trigger = this.host.triggers.get(state.triggerId);
94
+ if (!trigger) {
95
+ this.close('manual');
96
+ return;
97
+ }
98
+ const chosen = item ?? (this.activeIndex >= 0 ? this.items[this.activeIndex] : undefined) ?? this.items[0];
99
+ if (!chosen)
100
+ return;
101
+ const context = {
102
+ editor: this.host.editor,
103
+ document: this.host.document,
104
+ nodeIndex: state.nodeIndex,
105
+ triggerOffset: state.triggerOffset,
106
+ endOffset: state.endOffset,
107
+ query: state.query,
108
+ item: chosen,
109
+ };
110
+ this.close('select');
111
+ try {
112
+ if (trigger.select) {
113
+ await trigger.select(chosen, context);
114
+ }
115
+ else {
116
+ this.host.replaceRun(state, [createNodeFromSuggestion(trigger, chosen)], {
117
+ trailingSpace: true,
118
+ source: 'plugin',
119
+ });
120
+ }
121
+ }
122
+ catch (error) {
123
+ this.host.events.emit('error', { error: toError(error), phase: `trigger:${trigger.id}:select` });
124
+ }
125
+ }
126
+ /** Close the active trigger session. */
127
+ close(reason) {
128
+ const state = this.active;
129
+ if (!state)
130
+ return;
131
+ this.active = null;
132
+ this.items = [];
133
+ this.activeIndex = -1;
134
+ this.host.patchState({ activeTrigger: null, suggestions: [], activeSuggestionIndex: -1 });
135
+ this.host.events.emit('triggerClose', { triggerId: state.triggerId, reason });
136
+ }
137
+ evaluate() {
138
+ const document = this.host.document;
139
+ const selection = this.host.selection;
140
+ if (selection.anchor.nodeIndex !== selection.focus.nodeIndex ||
141
+ selection.anchor.offset !== selection.focus.offset) {
142
+ this.close('input');
143
+ return;
144
+ }
145
+ const node = document.nodes[selection.focus.nodeIndex];
146
+ if (!node || node.type !== 'text') {
147
+ this.close('input');
148
+ return;
149
+ }
150
+ const before = node.text.slice(0, selection.focus.offset);
151
+ let best = null;
152
+ for (const trigger of this.host.triggers.list()) {
153
+ const index = before.lastIndexOf(trigger.character);
154
+ if (index === -1)
155
+ continue;
156
+ const query = before.slice(index + 1);
157
+ if (query.includes('\n'))
158
+ continue;
159
+ if (!trigger.allowSpaces && /\s/.test(query))
160
+ continue;
161
+ if (!trigger.allowMidWord && index > 0 && WORD_CHAR.test(before[index - 1] ?? ''))
162
+ continue;
163
+ if (!best || index > best.state.triggerOffset) {
164
+ best = {
165
+ trigger,
166
+ state: {
167
+ triggerId: trigger.id,
168
+ query,
169
+ nodeIndex: selection.focus.nodeIndex,
170
+ triggerOffset: index,
171
+ endOffset: selection.focus.offset,
172
+ },
173
+ };
174
+ }
175
+ }
176
+ const previous = this.active;
177
+ if (!best) {
178
+ this.close('input');
179
+ return;
180
+ }
181
+ const changed = !previous ||
182
+ previous.triggerId !== best.state.triggerId ||
183
+ previous.query !== best.state.query ||
184
+ previous.nodeIndex !== best.state.nodeIndex ||
185
+ previous.triggerOffset !== best.state.triggerOffset;
186
+ this.active = best.state;
187
+ if (!previous || previous.triggerId !== best.trigger.id) {
188
+ this.host.events.emit('triggerOpen', { triggerId: best.trigger.id, query: best.state.query });
189
+ }
190
+ this.host.patchState({ activeTrigger: best.state });
191
+ if (changed) {
192
+ this.setSuggestions(best.state.triggerId, [], -1);
193
+ void this.runSearch(best.trigger, best.state);
194
+ }
195
+ }
196
+ async runSearch(trigger, state) {
197
+ const token = ++this.searchToken;
198
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : undefined;
199
+ try {
200
+ const results = await trigger.search({
201
+ query: state.query,
202
+ document: this.host.document,
203
+ selection: this.host.selection,
204
+ signal: controller?.signal,
205
+ });
206
+ if (token !== this.searchToken || this.active !== state)
207
+ return;
208
+ const items = (results ?? []).filter((item) => item && typeof item.id === 'string');
209
+ if (items.length === 0) {
210
+ this.setSuggestions(state.triggerId, [], -1);
211
+ return;
212
+ }
213
+ this.setSuggestions(state.triggerId, items, 0);
214
+ }
215
+ catch (error) {
216
+ if (token === this.searchToken) {
217
+ this.host.events.emit('error', { error: toError(error), phase: `trigger:${trigger.id}:search` });
218
+ }
219
+ }
220
+ }
221
+ setSuggestions(triggerId, items, activeIndex) {
222
+ this.items = items;
223
+ this.activeIndex = activeIndex;
224
+ this.host.patchState({ suggestions: items, activeSuggestionIndex: activeIndex });
225
+ this.host.events.emit('suggestionsChange', { triggerId: items.length ? triggerId : null, suggestions: items, activeIndex });
226
+ }
227
+ }
228
+ /** Convenience factory used by tests and plugins to build triggers quickly. */
229
+ export function defineTrigger(trigger) {
230
+ if (!trigger.character || trigger.character.length !== 1) {
231
+ throw new AIComposerError('UNKNOWN_TRIGGER', `Trigger "${trigger.id}" must define a single character`);
232
+ }
233
+ return trigger;
234
+ }
235
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/triggers/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /** Editor configuration types. */
2
+ import type { AIComposerDocument } from './model/document';
3
+ import type { AIComposerNode } from './model/nodes';
4
+ import type { AIComposerPlugin } from './plugins/registry';
5
+ import type { Serializer } from './serialization/serializers';
6
+ import type { NodeDefinition } from './nodes/registry';
7
+ import type { AIComposer } from './editor';
8
+ import type { HistoryOptions } from './history/history';
9
+ export type SubmitKey = 'enter' | 'shift-enter' | 'none';
10
+ export interface SubmitConfig {
11
+ /** Async handler invoked by editor.submit(). */
12
+ onSubmit?(value: AIComposerDocument, editor: AIComposer): void | Promise<void>;
13
+ /** Clear the document after a successful submit (default false). */
14
+ clearOnSubmit?: boolean;
15
+ /** Allow submitting an empty document (default false). */
16
+ allowEmpty?: boolean;
17
+ }
18
+ export interface AIComposerConfig {
19
+ /** Presentation mode / preset name — 'default' | 'compact' | 'chat' | 'expanded' | custom. */
20
+ mode?: string;
21
+ placeholder?: string;
22
+ disabled?: boolean;
23
+ readonly?: boolean;
24
+ submit?: SubmitConfig;
25
+ submitKey?: SubmitKey;
26
+ history?: HistoryOptions;
27
+ }
28
+ export interface AIComposerOptions extends AIComposerConfig {
29
+ /** Initial value: document, node list or plain string. */
30
+ value?: AIComposerDocument | AIComposerNode[] | string;
31
+ plugins?: AIComposerPlugin[];
32
+ /** Extra/overriding node type definitions. */
33
+ nodeTypes?: NodeDefinition[];
34
+ /** Extra/overriding serializers. */
35
+ serializers?: Serializer[];
36
+ }
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAElC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,QAAQ,CAAC,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,oEAAoE;IACpE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,8FAA8F;IAC9F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,0DAA0D;IAC1D,KAAK,CAAC,EAAE,kBAAkB,GAAG,cAAc,EAAE,GAAG,MAAM,CAAC;IACvD,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,8CAA8C;IAC9C,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B,oCAAoC;IACpC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Editor configuration types. */
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,kCAAkC"}
@@ -0,0 +1,3 @@
1
+ /** Escape a string for safe interpolation into HTML text/attribute contexts. */
2
+ export declare function escapeHtml(value: string): string;
3
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/utils/html.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD"}
@@ -0,0 +1,10 @@
1
+ /** Escape a string for safe interpolation into HTML text/attribute contexts. */
2
+ export function escapeHtml(value) {
3
+ return value
4
+ .replace(/&/g, '&amp;')
5
+ .replace(/</g, '&lt;')
6
+ .replace(/>/g, '&gt;')
7
+ .replace(/"/g, '&quot;')
8
+ .replace(/'/g, '&#39;');
9
+ }
10
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/utils/html.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@ai-composer/core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "peerDependencies": {},
17
+ "devDependencies": {
18
+ "vitest": "^2.1.8"
19
+ }
20
+ }
package/project.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "core",
3
+ "$schema": "../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/core/src",
5
+ "projectType": "library",
6
+ "tags": [
7
+ "scope:core",
8
+ "type:lib"
9
+ ],
10
+ "targets": {
11
+ "build": {
12
+ "executor": "@nx/js:tsc",
13
+ "outputs": [
14
+ "{options.outputPath}"
15
+ ],
16
+ "options": {
17
+ "outputPath": "packages/core/dist",
18
+ "main": "packages/core/src/index.ts",
19
+ "tsConfig": "packages/core/tsconfig.lib.json",
20
+ "rootDir": "packages/core/src",
21
+ "assets": [
22
+ {
23
+ "input": "../packages/core",
24
+ "glob": "*.md",
25
+ "output": "."
26
+ }
27
+ ],
28
+ "generateExportsIfAbsent": false
29
+ }
30
+ },
31
+ "test": {
32
+ "executor": "nx:run-commands",
33
+ "options": {
34
+ "command": "vitest run --project core"
35
+ }
36
+ },
37
+ "lint": {
38
+ "executor": "@nx/eslint:lint",
39
+ "outputs": [
40
+ "{options.outputFile}"
41
+ ],
42
+ "options": {
43
+ "lintFilePatterns": [
44
+ "packages/core/**/*.ts",
45
+ "packages/core/**/*.tsx"
46
+ ]
47
+ }
48
+ },
49
+ "typecheck": {
50
+ "executor": "nx:run-commands",
51
+ "options": {
52
+ "command": "tsc -p packages/core/tsconfig.spec.json --noEmit"
53
+ }
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Built-in commands. Plugins extend this set via
3
+ * their `commands` contribution.
4
+ */
5
+
6
+ import { AIComposerError } from '../errors';
7
+ import type { Position } from '../model/selection';
8
+ import type { AIComposerNode } from '../model/nodes';
9
+ import type { SuggestionItem } from '../state/state';
10
+ import type { AIComposer } from '../editor';
11
+ import type { AIComposerCommand } from './registry';
12
+
13
+ export const BUILTIN_COMMANDS = {
14
+ submit: 'submit',
15
+ clear: 'clear',
16
+ undo: 'undo',
17
+ redo: 'redo',
18
+ insertText: 'insertText',
19
+ insertNode: 'insertNode',
20
+ focus: 'focus',
21
+ blur: 'blur',
22
+ openTrigger: 'openTrigger',
23
+ removeNode: 'removeNode',
24
+ acceptSuggestion: 'acceptSuggestion',
25
+ } as const;
26
+
27
+ export interface InsertTextPayload {
28
+ text: string;
29
+ at?: Position;
30
+ }
31
+
32
+ export interface InsertNodePayload {
33
+ node: AIComposerNode;
34
+ at?: Position;
35
+ }
36
+
37
+ export interface RemoveNodePayload {
38
+ key: string;
39
+ }
40
+
41
+ export interface OpenTriggerPayload {
42
+ triggerId: string;
43
+ }
44
+
45
+ export interface AcceptSuggestionPayload {
46
+ item?: SuggestionItem;
47
+ }
48
+
49
+ function payloadOf<T>(payload: unknown): T {
50
+ return payload as T;
51
+ }
52
+
53
+ export function createBuiltinCommands(editor: AIComposer): AIComposerCommand[] {
54
+ const commands: AIComposerCommand[] = [
55
+ {
56
+ id: BUILTIN_COMMANDS.submit,
57
+ label: 'Submit',
58
+ canExecute: ({ state }) => !state.disabled && !state.readonly && !state.submitting,
59
+ execute: () => editor.submit(),
60
+ },
61
+ {
62
+ id: BUILTIN_COMMANDS.clear,
63
+ label: 'Clear',
64
+ canExecute: ({ state }) => !state.disabled && !state.readonly,
65
+ execute: () => editor.clear(),
66
+ },
67
+ {
68
+ id: BUILTIN_COMMANDS.undo,
69
+ label: 'Undo',
70
+ canExecute: ({ state }) => state.canUndo,
71
+ execute: () => editor.undo(),
72
+ },
73
+ {
74
+ id: BUILTIN_COMMANDS.redo,
75
+ label: 'Redo',
76
+ canExecute: ({ state }) => state.canRedo,
77
+ execute: () => editor.redo(),
78
+ },
79
+ {
80
+ id: BUILTIN_COMMANDS.insertText,
81
+ label: 'Insert text',
82
+ execute: (_context) => {
83
+ const payload = payloadOf<InsertTextPayload | string>(_context.payload);
84
+ if (!payload) throw new AIComposerError('UNKNOWN_COMMAND', 'insertText requires a payload');
85
+ if (typeof payload === 'string') {
86
+ editor.insertText(payload);
87
+ } else {
88
+ editor.insertText(payload.text, payload.at);
89
+ }
90
+ },
91
+ },
92
+ {
93
+ id: BUILTIN_COMMANDS.insertNode,
94
+ label: 'Insert node',
95
+ execute: (_context) => {
96
+ const payload = payloadOf<InsertNodePayload>(_context.payload);
97
+ if (!payload?.node) throw new AIComposerError('UNKNOWN_COMMAND', 'insertNode requires { node }');
98
+ editor.insertNode(payload.node, { at: payload.at });
99
+ },
100
+ },
101
+ {
102
+ id: BUILTIN_COMMANDS.focus,
103
+ label: 'Focus',
104
+ execute: () => editor.focus(),
105
+ },
106
+ {
107
+ id: BUILTIN_COMMANDS.blur,
108
+ label: 'Blur',
109
+ execute: () => editor.blur(),
110
+ },
111
+ {
112
+ id: BUILTIN_COMMANDS.openTrigger,
113
+ label: 'Open trigger',
114
+ execute: (_context) => {
115
+ const payload = payloadOf<OpenTriggerPayload | string>(_context.payload);
116
+ const triggerId = typeof payload === 'string' ? payload : payload?.triggerId;
117
+ if (!triggerId) throw new AIComposerError('UNKNOWN_TRIGGER', 'openTrigger requires a triggerId');
118
+ editor.openTrigger(triggerId);
119
+ },
120
+ },
121
+ {
122
+ id: BUILTIN_COMMANDS.removeNode,
123
+ label: 'Remove node',
124
+ execute: (_context) => {
125
+ const payload = payloadOf<RemoveNodePayload | string>(_context.payload);
126
+ const key = typeof payload === 'string' ? payload : payload?.key;
127
+ if (!key) throw new AIComposerError('UNKNOWN_COMMAND', 'removeNode requires a key');
128
+ editor.removeNode(key);
129
+ },
130
+ },
131
+ {
132
+ id: BUILTIN_COMMANDS.acceptSuggestion,
133
+ label: 'Accept suggestion',
134
+ execute: async (_context) => {
135
+ const payload = payloadOf<AcceptSuggestionPayload | undefined>(_context.payload);
136
+ await editor.acceptSuggestion(payload?.item);
137
+ },
138
+ },
139
+ ];
140
+
141
+ return commands;
142
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Command system — UI-independent named operations. Toolbars and keyboard
3
+ * shortcuts bind to command ids, never to implementation functions.
4
+ */
5
+
6
+ import type { Unsubscribe } from '../events/event-bus';
7
+ import type { AIComposer } from '../editor';
8
+ import type { AIComposerState } from '../state/state';
9
+
10
+ export interface CommandContext {
11
+ editor: AIComposer;
12
+ state: AIComposerState;
13
+ payload?: unknown;
14
+ }
15
+
16
+ export interface AIComposerCommand {
17
+ id: string;
18
+ label?: string;
19
+ execute(context: CommandContext): void | Promise<void>;
20
+ canExecute?(context: CommandContext): boolean;
21
+ }
22
+
23
+ export interface CommandRegistry {
24
+ register(command: AIComposerCommand): Unsubscribe;
25
+ get(id: string): AIComposerCommand | undefined;
26
+ has(id: string): boolean;
27
+ list(): AIComposerCommand[];
28
+ clear(): void;
29
+ }
30
+
31
+ export function createCommandRegistry(): CommandRegistry {
32
+ const commands = new Map<string, AIComposerCommand>();
33
+ return {
34
+ register(command) {
35
+ commands.set(command.id, command);
36
+ return () => {
37
+ if (commands.get(command.id) === command) commands.delete(command.id);
38
+ };
39
+ },
40
+ get(id) {
41
+ return commands.get(id);
42
+ },
43
+ has(id) {
44
+ return commands.has(id);
45
+ },
46
+ list() {
47
+ return [...commands.values()];
48
+ },
49
+ clear() {
50
+ commands.clear();
51
+ },
52
+ };
53
+ }