@modelnex/sdk 0.1.1
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/README.md +66 -0
- package/dist/aom-J6NYMGDW.mjs +69 -0
- package/dist/dom-sync-L5KIP45X.mjs +55 -0
- package/dist/index.d.mts +562 -0
- package/dist/index.d.ts +562 -0
- package/dist/index.js +6004 -0
- package/dist/index.mjs +5808 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @modelnex/sdk
|
|
2
|
+
|
|
3
|
+
React SDK for natural language control of web apps. Register actions and context; an AI agent executes actions in response to user commands.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @modelnex/sdk react zod
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { ModelNexProvider, ModelNexChatBubble } from "@modelnex/sdk";
|
|
15
|
+
|
|
16
|
+
function App() {
|
|
17
|
+
const [items, setItems] = React.useState([]);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div>
|
|
21
|
+
<ModelNexChatBubble placeholder="Add item, list all..." />
|
|
22
|
+
<ul>{items.map(i => <li key={i.id}>{i.title}</li>)}</ul>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Wrap your app (serverUrl defaults to http://localhost:3002)
|
|
28
|
+
export default () => (
|
|
29
|
+
<ModelNexProvider>
|
|
30
|
+
<App />
|
|
31
|
+
</ModelNexProvider>
|
|
32
|
+
);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Exports
|
|
36
|
+
|
|
37
|
+
| Export | Purpose |
|
|
38
|
+
|--------|---------|
|
|
39
|
+
| `ModelNexProvider` | Wraps app, connects to agent server |
|
|
40
|
+
| `useRunCommand` | Run commands programmatically (for custom UI) |
|
|
41
|
+
| `UIStateProvider` | Holds UI state synced to agent |
|
|
42
|
+
| `useUIState` | Read/update UI state |
|
|
43
|
+
| `useViewportTrack` | Track element visibility |
|
|
44
|
+
| `useVisibleIds` | Get visible element IDs |
|
|
45
|
+
| `useAgentViewport` | Register visible IDs with agent |
|
|
46
|
+
| `ModelNexChatBubble` | Optional chat bubble UI |
|
|
47
|
+
|
|
48
|
+
## Custom UI
|
|
49
|
+
|
|
50
|
+
Use `useRunCommand` instead of `ModelNexChatBubble` for your own command UI:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
const runCommand = useRunCommand();
|
|
54
|
+
|
|
55
|
+
<button onClick={() => runCommand("Add a new card")}>Run</button>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Peer Dependencies
|
|
60
|
+
|
|
61
|
+
- `react` >= 17
|
|
62
|
+
- `zod` >= 3
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/utils/aom.ts
|
|
2
|
+
var uidMap = /* @__PURE__ */ new Map();
|
|
3
|
+
var nextUid = 1;
|
|
4
|
+
function generateMinifiedAOM() {
|
|
5
|
+
uidMap.clear();
|
|
6
|
+
nextUid = 1;
|
|
7
|
+
const interactives = document.querySelectorAll(
|
|
8
|
+
'button, a, input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"]'
|
|
9
|
+
);
|
|
10
|
+
const nodes = [];
|
|
11
|
+
interactives.forEach((el) => {
|
|
12
|
+
if (!el.offsetParent && (el.offsetWidth === 0 || el.offsetHeight === 0)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (el.closest("#modelnex-studio-root") || el.closest("#modelnex-active-agent-root")) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const uid = `node:${nextUid++}`;
|
|
19
|
+
uidMap.set(uid, el);
|
|
20
|
+
let text = (el.textContent || "").replace(/\s+/g, " ").trim();
|
|
21
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
22
|
+
const placeholder = el.getAttribute("placeholder");
|
|
23
|
+
let value = void 0;
|
|
24
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
25
|
+
value = el.value;
|
|
26
|
+
}
|
|
27
|
+
let role = el.tagName.toLowerCase();
|
|
28
|
+
if (el.hasAttribute("role")) {
|
|
29
|
+
role = el.getAttribute("role");
|
|
30
|
+
} else if (role === "a") {
|
|
31
|
+
role = "link";
|
|
32
|
+
} else if (el instanceof HTMLInputElement) {
|
|
33
|
+
role = el.type ? `input[${el.type}]` : "input";
|
|
34
|
+
}
|
|
35
|
+
const node = { uid, role };
|
|
36
|
+
const displayLabel = ariaLabel || text || placeholder;
|
|
37
|
+
if (displayLabel) {
|
|
38
|
+
node.text = displayLabel.substring(0, 100);
|
|
39
|
+
}
|
|
40
|
+
if (el.getAttribute("name")) {
|
|
41
|
+
node.name = el.getAttribute("name");
|
|
42
|
+
}
|
|
43
|
+
if (value) {
|
|
44
|
+
node.value = value.substring(0, 100);
|
|
45
|
+
}
|
|
46
|
+
if (el instanceof HTMLAnchorElement && el.href) {
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(el.href);
|
|
49
|
+
node.href = url.pathname + url.search + url.hash;
|
|
50
|
+
} catch {
|
|
51
|
+
node.href = el.getAttribute("href");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
nodes.push(node);
|
|
55
|
+
});
|
|
56
|
+
return { nodes };
|
|
57
|
+
}
|
|
58
|
+
function getElementByUid(uid) {
|
|
59
|
+
return uidMap.get(uid) || null;
|
|
60
|
+
}
|
|
61
|
+
function clearAOMMap() {
|
|
62
|
+
uidMap.clear();
|
|
63
|
+
nextUid = 1;
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
clearAOMMap,
|
|
67
|
+
generateMinifiedAOM,
|
|
68
|
+
getElementByUid
|
|
69
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/utils/dom-sync.ts
|
|
2
|
+
function waitForDomSettle(options = {}) {
|
|
3
|
+
const { timeoutMs = 5e3, debounceMs = 400, minWaitMs = 100 } = options;
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
let debounceTimer = null;
|
|
6
|
+
let resolved = false;
|
|
7
|
+
const maxTimer = setTimeout(() => {
|
|
8
|
+
if (!resolved) {
|
|
9
|
+
resolved = true;
|
|
10
|
+
cleanup();
|
|
11
|
+
console.log("[DOM Sync] Forced resolution by max timeout");
|
|
12
|
+
resolve();
|
|
13
|
+
}
|
|
14
|
+
}, Math.max(timeoutMs, minWaitMs));
|
|
15
|
+
const finish = () => {
|
|
16
|
+
if (!resolved) {
|
|
17
|
+
resolved = true;
|
|
18
|
+
cleanup();
|
|
19
|
+
resolve();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const observer = new MutationObserver((mutations) => {
|
|
23
|
+
const hasSignificantMutations = mutations.some((m) => {
|
|
24
|
+
if (m.target instanceof HTMLElement) {
|
|
25
|
+
if (m.target.hasAttribute("data-modelnex-tour-highlight")) return false;
|
|
26
|
+
if (m.target.hasAttribute("data-modelnex-caption")) return false;
|
|
27
|
+
if (m.target.closest("#modelnex-studio-root")) return false;
|
|
28
|
+
if (m.target.closest("#modelnex-active-agent-root")) return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
});
|
|
32
|
+
if (!hasSignificantMutations) return;
|
|
33
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
34
|
+
debounceTimer = setTimeout(finish, debounceMs);
|
|
35
|
+
});
|
|
36
|
+
const cleanup = () => {
|
|
37
|
+
observer.disconnect();
|
|
38
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
39
|
+
clearTimeout(maxTimer);
|
|
40
|
+
};
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
if (resolved) return;
|
|
43
|
+
observer.observe(document.body, {
|
|
44
|
+
childList: true,
|
|
45
|
+
subtree: true,
|
|
46
|
+
attributes: true,
|
|
47
|
+
characterData: true
|
|
48
|
+
});
|
|
49
|
+
debounceTimer = setTimeout(finish, debounceMs);
|
|
50
|
+
}, minWaitMs);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
waitForDomSettle
|
|
55
|
+
};
|