@sampleapp.ai/sdk 1.0.42 → 1.0.43
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/dist/components/sandbox/Sandbox.js +50 -29
- package/dist/components/sandbox/api.js +73 -1
- package/dist/components/sandbox/guardian/demo/guardian-demo.js +3 -3
- package/dist/components/sandbox/guardian/guardian-component.js +5 -3
- package/dist/components/sandbox/guardian/guardian-playground.js +10 -14
- package/dist/components/sandbox/guardian/header.js +26 -39
- package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +16 -4
- package/dist/components/sandbox/guardian/index.js +1 -0
- package/dist/components/sandbox/guardian/right-view/pill-file-selector.js +68 -18
- package/dist/components/sandbox/guardian/right-view/preview-control-bar.js +5 -2
- package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +3 -4
- package/dist/components/sandbox/guardian/right-view/right-view.js +2 -2
- package/dist/components/sandbox/guardian/right-view/simplified-editor.js +9 -2
- package/dist/components/sandbox/guardian/utils.js +4 -16
- package/dist/hooks/use-tree-selector.js +98 -0
- package/dist/index.d.ts +579 -20
- package/dist/index.es.js +17937 -17550
- package/dist/index.js +12 -4
- package/dist/lib/api-client.js +68 -0
- package/dist/lib/content-matcher.js +99 -0
- package/dist/lib/shadow-dom-wrapper.js +31 -10
- package/dist/lib/tree-utils.js +191 -0
- package/dist/lib/types/tree-config.js +18 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -15,8 +15,16 @@ const SandboxHome = (props) => React.createElement(ShadowDomWrapper, null, React
|
|
|
15
15
|
export { ChatButton, ModalSearchAndChat, ChatBar, TailwindExample, SandboxHome };
|
|
16
16
|
// Export Sandbox component and types
|
|
17
17
|
export { Sandbox } from "./components/sandbox";
|
|
18
|
+
// Export sandbox API functions
|
|
19
|
+
export { fetchSandboxConfig, fetchSandboxConfigWithContent, buildConfigFromContent } from "./components/sandbox/api";
|
|
20
|
+
// Export tree selector utilities and types
|
|
21
|
+
export { useTreeSelector } from "./hooks/use-tree-selector";
|
|
22
|
+
export { getNodeAtPath, getNodesAlongPath, pathToByType, getDefaultPath, getSelectableSections, updateSelectionPath, createSelection, isPathComplete, getNodeLabel } from "./lib/tree-utils";
|
|
23
|
+
export { findMatchingContent, hasContentForSelection, getUniqueFieldValues, filterContent } from "./lib/content-matcher";
|
|
24
|
+
// Export API client
|
|
25
|
+
export { createApiClient, extractContainerIdFromUrl } from "./lib/api-client";
|
|
18
26
|
// Export Guardian components (now nested under sandbox)
|
|
19
|
-
export { GuardianPlayground, GuardianComponent, GuardianProvider, VmProvider, buildGuardianConfig, } from "./components/sandbox/guardian";
|
|
27
|
+
export { GuardianPlayground, GuardianComponent, GuardianProvider, VmProvider, buildGuardianConfig, Header } from "./components/sandbox/guardian";
|
|
20
28
|
// Export themes
|
|
21
29
|
export { themes, getTheme, DEFAULT_THEME } from "./themes";
|
|
22
30
|
// SDK class for loadScript programmatic usage
|
|
@@ -33,7 +41,7 @@ class SampleAppSDK {
|
|
|
33
41
|
onClose: () => {
|
|
34
42
|
root.unmount();
|
|
35
43
|
modalContainer.remove();
|
|
36
|
-
}
|
|
44
|
+
}
|
|
37
45
|
});
|
|
38
46
|
root.render(component);
|
|
39
47
|
return {
|
|
@@ -46,7 +54,7 @@ class SampleAppSDK {
|
|
|
46
54
|
},
|
|
47
55
|
hide: () => {
|
|
48
56
|
modalContainer.style.display = "none";
|
|
49
|
-
}
|
|
57
|
+
}
|
|
50
58
|
};
|
|
51
59
|
};
|
|
52
60
|
this.ChatButton = (settings, container) => {
|
|
@@ -58,7 +66,7 @@ class SampleAppSDK {
|
|
|
58
66
|
unmount: () => {
|
|
59
67
|
root.unmount();
|
|
60
68
|
targetContainer.remove();
|
|
61
|
-
}
|
|
69
|
+
}
|
|
62
70
|
};
|
|
63
71
|
};
|
|
64
72
|
}
|
package/dist/lib/api-client.js
CHANGED
|
@@ -74,6 +74,74 @@ class ApiClient {
|
|
|
74
74
|
method: "GET",
|
|
75
75
|
}, { skip, limit });
|
|
76
76
|
},
|
|
77
|
+
/**
|
|
78
|
+
* Get all sandbox contents for a specific use case
|
|
79
|
+
* Used for upfront loading to enable local filtering
|
|
80
|
+
* @param useCaseId - The ID of the use case
|
|
81
|
+
* @param skip - Number of records to skip (default: 0)
|
|
82
|
+
* @param limit - Maximum number of records to return (default: 100)
|
|
83
|
+
* @returns Array of sandbox content data
|
|
84
|
+
*/
|
|
85
|
+
getByUseCaseId: async (useCaseId, skip = 0, limit = 100) => {
|
|
86
|
+
return this.request(["sandbox-content", "use-case", useCaseId.toString()], {
|
|
87
|
+
method: "GET",
|
|
88
|
+
}, { skip, limit });
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Sandbox use case endpoints
|
|
93
|
+
*/
|
|
94
|
+
this.sandboxUseCase = {
|
|
95
|
+
/**
|
|
96
|
+
* Get a sandbox use case by UID
|
|
97
|
+
* @param uid - The UID of the use case
|
|
98
|
+
* @returns The use case data including tech_stack_config
|
|
99
|
+
*/
|
|
100
|
+
getByUid: async (uid) => {
|
|
101
|
+
return this.request(["sandbox-use-case", uid]);
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* Get a sandbox use case by ID
|
|
105
|
+
* @param id - The numeric ID of the use case
|
|
106
|
+
* @returns The use case data including tech_stack_config
|
|
107
|
+
*/
|
|
108
|
+
getById: async (id) => {
|
|
109
|
+
return this.request(["sandbox-use-case", "id", id.toString()]);
|
|
110
|
+
},
|
|
111
|
+
/**
|
|
112
|
+
* Get all sandbox use cases for a specific playground
|
|
113
|
+
* @param playgroundUid - The UID of the playground
|
|
114
|
+
* @param skip - Number of records to skip (default: 0)
|
|
115
|
+
* @param limit - Maximum number of records to return (default: 100)
|
|
116
|
+
* @returns Array of use case data
|
|
117
|
+
*/
|
|
118
|
+
getByPlayground: async (playgroundUid, skip = 0, limit = 100) => {
|
|
119
|
+
return this.request(["sandbox-use-case", "playground", playgroundUid], {
|
|
120
|
+
method: "GET",
|
|
121
|
+
}, { skip, limit });
|
|
122
|
+
},
|
|
123
|
+
/**
|
|
124
|
+
* Get all template use cases (public access)
|
|
125
|
+
* @param skip - Number of records to skip (default: 0)
|
|
126
|
+
* @param limit - Maximum number of records to return (default: 100)
|
|
127
|
+
* @returns Array of template use case data
|
|
128
|
+
*/
|
|
129
|
+
getTemplates: async (skip = 0, limit = 100) => {
|
|
130
|
+
return this.request(["sandbox-use-case", "templates", "list"], {
|
|
131
|
+
method: "GET",
|
|
132
|
+
}, { skip, limit });
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* Get all public use cases
|
|
136
|
+
* @param skip - Number of records to skip (default: 0)
|
|
137
|
+
* @param limit - Maximum number of records to return (default: 100)
|
|
138
|
+
* @returns Array of public use case data
|
|
139
|
+
*/
|
|
140
|
+
getPublic: async (skip = 0, limit = 100) => {
|
|
141
|
+
return this.request(["sandbox-use-case", "public", "list"], {
|
|
142
|
+
method: "GET",
|
|
143
|
+
}, { skip, limit });
|
|
144
|
+
},
|
|
77
145
|
};
|
|
78
146
|
/**
|
|
79
147
|
* SDK endpoints
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for matching sandbox content to tree selections
|
|
3
|
+
* Enables local filtering without API calls after initial load
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Find sandbox content that matches the current tree selection
|
|
7
|
+
* Matches against flat fields: product, product_version, architecture, frontend, backend, framework
|
|
8
|
+
*
|
|
9
|
+
* @param allContent - All sandbox content entries for the use case
|
|
10
|
+
* @param selection - Current tree selection
|
|
11
|
+
* @returns Matching content or null if not found
|
|
12
|
+
*/
|
|
13
|
+
export function findMatchingContent(allContent, selection) {
|
|
14
|
+
const { byType } = selection;
|
|
15
|
+
const match = allContent.find((content) => {
|
|
16
|
+
// Match each selected dimension against content fields
|
|
17
|
+
// Only check fields that are present in the selection
|
|
18
|
+
if (byType.product && content.product !== byType.product)
|
|
19
|
+
return false;
|
|
20
|
+
if (byType.version && content.product_version !== byType.version)
|
|
21
|
+
return false;
|
|
22
|
+
if (byType.architecture && content.architecture !== byType.architecture)
|
|
23
|
+
return false;
|
|
24
|
+
if (byType.frontend && content.frontend !== byType.frontend)
|
|
25
|
+
return false;
|
|
26
|
+
if (byType.backend && content.backend !== byType.backend)
|
|
27
|
+
return false;
|
|
28
|
+
if (byType.framework && content.framework !== byType.framework)
|
|
29
|
+
return false;
|
|
30
|
+
return true;
|
|
31
|
+
});
|
|
32
|
+
return match || null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if content exists for a potential selection
|
|
36
|
+
* Used to disable/gray out options that have no content
|
|
37
|
+
*
|
|
38
|
+
* @param allContent - All sandbox content entries for the use case
|
|
39
|
+
* @param partialSelection - Partial selection to check (only specified fields are matched)
|
|
40
|
+
* @returns True if at least one content entry matches
|
|
41
|
+
*/
|
|
42
|
+
export function hasContentForSelection(allContent, partialSelection) {
|
|
43
|
+
return allContent.some((content) => {
|
|
44
|
+
if (partialSelection.product &&
|
|
45
|
+
content.product !== partialSelection.product)
|
|
46
|
+
return false;
|
|
47
|
+
if (partialSelection.version &&
|
|
48
|
+
content.product_version !== partialSelection.version)
|
|
49
|
+
return false;
|
|
50
|
+
if (partialSelection.architecture &&
|
|
51
|
+
content.architecture !== partialSelection.architecture)
|
|
52
|
+
return false;
|
|
53
|
+
if (partialSelection.frontend &&
|
|
54
|
+
content.frontend !== partialSelection.frontend)
|
|
55
|
+
return false;
|
|
56
|
+
if (partialSelection.backend &&
|
|
57
|
+
content.backend !== partialSelection.backend)
|
|
58
|
+
return false;
|
|
59
|
+
if (partialSelection.framework &&
|
|
60
|
+
content.framework !== partialSelection.framework)
|
|
61
|
+
return false;
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get all unique values for a specific field from content entries
|
|
67
|
+
* Useful for building filter options
|
|
68
|
+
*
|
|
69
|
+
* @param allContent - All sandbox content entries
|
|
70
|
+
* @param field - The field to extract unique values from
|
|
71
|
+
* @returns Array of unique non-null values
|
|
72
|
+
*/
|
|
73
|
+
export function getUniqueFieldValues(allContent, field) {
|
|
74
|
+
const values = new Set();
|
|
75
|
+
for (const content of allContent) {
|
|
76
|
+
const value = content[field];
|
|
77
|
+
if (value) {
|
|
78
|
+
values.add(value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return Array.from(values);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Filter content entries by multiple criteria
|
|
85
|
+
*
|
|
86
|
+
* @param allContent - All sandbox content entries
|
|
87
|
+
* @param filters - Object with field names and values to filter by
|
|
88
|
+
* @returns Filtered array of content entries
|
|
89
|
+
*/
|
|
90
|
+
export function filterContent(allContent, filters) {
|
|
91
|
+
return allContent.filter((content) => {
|
|
92
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
93
|
+
if (value && content[key] !== value) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -9,13 +9,25 @@ export const ShadowDomWrapper = ({ children, }) => {
|
|
|
9
9
|
const containerRef = useRef(null);
|
|
10
10
|
const shadowRootRef = useRef(null);
|
|
11
11
|
const mountPointRef = useRef(null);
|
|
12
|
+
const rootRef = useRef(null);
|
|
13
|
+
// Initialize shadow DOM once on mount
|
|
12
14
|
useEffect(() => {
|
|
13
15
|
if (!containerRef.current)
|
|
14
16
|
return;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
let shadow;
|
|
18
|
+
// Check if shadow root already exists (e.g., from hot reload)
|
|
19
|
+
if (containerRef.current.shadowRoot) {
|
|
20
|
+
shadow = containerRef.current.shadowRoot;
|
|
21
|
+
shadowRootRef.current = shadow;
|
|
22
|
+
// Clear existing content to prevent stale roots
|
|
23
|
+
shadow.innerHTML = '';
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Create shadow DOM
|
|
27
|
+
shadow = containerRef.current.attachShadow({ mode: "open" });
|
|
28
|
+
shadowRootRef.current = shadow;
|
|
29
|
+
}
|
|
30
|
+
// Always create fresh mount point
|
|
19
31
|
const mountPoint = document.createElement("div");
|
|
20
32
|
mountPoint.id = "sdk-mount-point";
|
|
21
33
|
// Don't reset all styles - only reset what's necessary to prevent host page interference
|
|
@@ -28,15 +40,24 @@ export const ShadowDomWrapper = ({ children, }) => {
|
|
|
28
40
|
styleElement.textContent = TAILWIND_CSS;
|
|
29
41
|
shadow.appendChild(styleElement);
|
|
30
42
|
shadow.appendChild(mountPoint);
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
root.render(children);
|
|
43
|
+
// Create React root
|
|
44
|
+
rootRef.current = createRoot(mountPoint);
|
|
34
45
|
return () => {
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
46
|
+
// Schedule unmount for after current render completes
|
|
47
|
+
if (rootRef.current) {
|
|
48
|
+
const root = rootRef.current;
|
|
49
|
+
rootRef.current = null;
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
root.unmount();
|
|
52
|
+
}, 0);
|
|
38
53
|
}
|
|
39
54
|
};
|
|
55
|
+
}, []); // Only run once on mount
|
|
56
|
+
// Update rendered content when children change
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (rootRef.current) {
|
|
59
|
+
rootRef.current.render(children);
|
|
60
|
+
}
|
|
40
61
|
}, [children]);
|
|
41
62
|
return React.createElement("div", { ref: containerRef, style: { display: "contents" } });
|
|
42
63
|
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for navigating and manipulating the tech stack tree
|
|
3
|
+
*/
|
|
4
|
+
import { NODE_TYPE_LABELS } from "./types/tree-config";
|
|
5
|
+
/**
|
|
6
|
+
* Get the display label for a node (falls back to key if no label)
|
|
7
|
+
*/
|
|
8
|
+
export function getNodeLabel(node) {
|
|
9
|
+
return node.label || node.key;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Navigate the tree to find a node at a specific path
|
|
13
|
+
* @param root - The root node to start from
|
|
14
|
+
* @param path - Array of keys to follow
|
|
15
|
+
* @returns The node at the path, or null if not found
|
|
16
|
+
*/
|
|
17
|
+
export function getNodeAtPath(root, path) {
|
|
18
|
+
let current = root;
|
|
19
|
+
for (const key of path) {
|
|
20
|
+
if (!(current === null || current === void 0 ? void 0 : current.children))
|
|
21
|
+
return null;
|
|
22
|
+
const child = current.children.find((c) => c.key === key);
|
|
23
|
+
if (!child)
|
|
24
|
+
return null;
|
|
25
|
+
current = child;
|
|
26
|
+
}
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get all nodes along a path (including the root)
|
|
31
|
+
* @param root - The root node
|
|
32
|
+
* @param path - Array of keys to follow
|
|
33
|
+
* @returns Array of nodes from root to the final node
|
|
34
|
+
*/
|
|
35
|
+
export function getNodesAlongPath(root, path) {
|
|
36
|
+
const nodes = [root];
|
|
37
|
+
let current = root;
|
|
38
|
+
for (const key of path) {
|
|
39
|
+
if (!current.children)
|
|
40
|
+
break;
|
|
41
|
+
const child = current.children.find((c) => c.key === key);
|
|
42
|
+
if (!child)
|
|
43
|
+
break;
|
|
44
|
+
nodes.push(child);
|
|
45
|
+
current = child;
|
|
46
|
+
}
|
|
47
|
+
return nodes;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Convert a path to a byType map
|
|
51
|
+
* @param root - The root node
|
|
52
|
+
* @param path - Array of keys
|
|
53
|
+
* @returns Map of nodeType to selected key
|
|
54
|
+
*/
|
|
55
|
+
export function pathToByType(root, path) {
|
|
56
|
+
const byType = {};
|
|
57
|
+
const nodes = getNodesAlongPath(root, path);
|
|
58
|
+
// Skip the root node (index 0), start from first actual selection
|
|
59
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
60
|
+
const node = nodes[i];
|
|
61
|
+
if (node.nodeType && node.nodeType !== "root") {
|
|
62
|
+
byType[node.nodeType] = node.key;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return byType;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get the default path through the tree (following isDefault flags or first child)
|
|
69
|
+
* @param root - The root node
|
|
70
|
+
* @returns Array of keys representing the default selection path
|
|
71
|
+
*/
|
|
72
|
+
export function getDefaultPath(root) {
|
|
73
|
+
const path = [];
|
|
74
|
+
let current = root;
|
|
75
|
+
while (current.children && current.children.length > 0) {
|
|
76
|
+
// Find default child or use first child
|
|
77
|
+
const defaultChild = current.children.find((c) => c.isDefault) || current.children[0];
|
|
78
|
+
path.push(defaultChild.key);
|
|
79
|
+
current = defaultChild;
|
|
80
|
+
}
|
|
81
|
+
return path;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get selectable sections based on current selection path
|
|
85
|
+
* This builds the button groups shown in the header
|
|
86
|
+
* @param config - The tech stack configuration
|
|
87
|
+
* @param currentPath - Current selection path
|
|
88
|
+
* @returns Array of sections with their options
|
|
89
|
+
*/
|
|
90
|
+
export function getSelectableSections(config, currentPath) {
|
|
91
|
+
const sections = [];
|
|
92
|
+
const nodes = getNodesAlongPath(config.root, currentPath);
|
|
93
|
+
// For each level in the path, create a section showing siblings
|
|
94
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
95
|
+
const node = nodes[i];
|
|
96
|
+
// Skip root node
|
|
97
|
+
if (node.nodeType === "root")
|
|
98
|
+
continue;
|
|
99
|
+
// Get parent to find siblings
|
|
100
|
+
const parent = i === 0 ? config.root : nodes[i - 1];
|
|
101
|
+
if (!parent.children)
|
|
102
|
+
continue;
|
|
103
|
+
const options = parent.children.map((sibling) => ({
|
|
104
|
+
key: sibling.key,
|
|
105
|
+
label: getNodeLabel(sibling),
|
|
106
|
+
isSelected: sibling.key === node.key
|
|
107
|
+
}));
|
|
108
|
+
sections.push({
|
|
109
|
+
nodeType: node.nodeType,
|
|
110
|
+
label: NODE_TYPE_LABELS[node.nodeType] || `${node.nodeType}:`,
|
|
111
|
+
options
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// Add the next level if current node has children
|
|
115
|
+
const currentNode = getNodeAtPath(config.root, currentPath);
|
|
116
|
+
if ((currentNode === null || currentNode === void 0 ? void 0 : currentNode.children) && currentNode.children.length > 0) {
|
|
117
|
+
const firstChild = currentNode.children[0];
|
|
118
|
+
const options = currentNode.children.map((child) => ({
|
|
119
|
+
key: child.key,
|
|
120
|
+
label: getNodeLabel(child),
|
|
121
|
+
isSelected: false // None selected yet at this level
|
|
122
|
+
}));
|
|
123
|
+
sections.push({
|
|
124
|
+
nodeType: firstChild.nodeType,
|
|
125
|
+
label: NODE_TYPE_LABELS[firstChild.nodeType] || `${firstChild.nodeType}:`,
|
|
126
|
+
options
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return sections;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Update the selection path when a user selects an option
|
|
133
|
+
* @param config - The tech stack configuration
|
|
134
|
+
* @param currentPath - Current selection path
|
|
135
|
+
* @param nodeType - The nodeType of the selection being changed
|
|
136
|
+
* @param selectedKey - The key of the newly selected option
|
|
137
|
+
* @returns New selection path
|
|
138
|
+
*/
|
|
139
|
+
export function updateSelectionPath(config, currentPath, nodeType, selectedKey) {
|
|
140
|
+
var _a;
|
|
141
|
+
const nodes = getNodesAlongPath(config.root, currentPath);
|
|
142
|
+
// Find which level this nodeType is at
|
|
143
|
+
let levelIndex = -1;
|
|
144
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
145
|
+
if (nodes[i].nodeType === nodeType) {
|
|
146
|
+
levelIndex = i - 1; // Index in path (nodes includes root, path doesn't)
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// If not found in current path, it might be the next level
|
|
151
|
+
if (levelIndex === -1) {
|
|
152
|
+
const currentNode = getNodeAtPath(config.root, currentPath);
|
|
153
|
+
if ((currentNode === null || currentNode === void 0 ? void 0 : currentNode.children) &&
|
|
154
|
+
((_a = currentNode.children[0]) === null || _a === void 0 ? void 0 : _a.nodeType) === nodeType) {
|
|
155
|
+
// Append to current path
|
|
156
|
+
return [...currentPath, selectedKey];
|
|
157
|
+
}
|
|
158
|
+
// Unknown level, return current path
|
|
159
|
+
return currentPath;
|
|
160
|
+
}
|
|
161
|
+
// Truncate path at this level and set new selection
|
|
162
|
+
const newPath = currentPath.slice(0, levelIndex);
|
|
163
|
+
newPath.push(selectedKey);
|
|
164
|
+
// Follow defaults for remaining levels
|
|
165
|
+
const newNode = getNodeAtPath(config.root, newPath);
|
|
166
|
+
if (newNode) {
|
|
167
|
+
let current = newNode;
|
|
168
|
+
while (current.children && current.children.length > 0) {
|
|
169
|
+
const defaultChild = current.children.find((c) => c.isDefault) || current.children[0];
|
|
170
|
+
newPath.push(defaultChild.key);
|
|
171
|
+
current = defaultChild;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return newPath;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Create a TreeSelection object from a path
|
|
178
|
+
*/
|
|
179
|
+
export function createSelection(config, path) {
|
|
180
|
+
return {
|
|
181
|
+
path,
|
|
182
|
+
byType: pathToByType(config.root, path)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if a path is complete (reaches a leaf node)
|
|
187
|
+
*/
|
|
188
|
+
export function isPathComplete(config, path) {
|
|
189
|
+
const node = getNodeAtPath(config.root, path);
|
|
190
|
+
return node !== null && (!node.children || node.children.length === 0);
|
|
191
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree configuration types for dynamic tech stack selection
|
|
3
|
+
* Matches the tech_stack_config structure from SandboxUseCase
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Human-readable labels for node types
|
|
7
|
+
*/
|
|
8
|
+
export const NODE_TYPE_LABELS = {
|
|
9
|
+
root: "",
|
|
10
|
+
product: "Product:",
|
|
11
|
+
version: "Version:",
|
|
12
|
+
architecture: "Architecture:",
|
|
13
|
+
frontend: "Frontend:",
|
|
14
|
+
backend: "Backend:",
|
|
15
|
+
framework: "Framework:",
|
|
16
|
+
integration: "Integration Path:",
|
|
17
|
+
platform: "Platform:",
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sampleapp.ai/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.43",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TypeScript SDK for your components",
|
|
6
6
|
"main": "./dist/index.umd.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"preview": "vite preview",
|
|
34
34
|
"type-check": "tsc --noEmit",
|
|
35
35
|
"test:api-client": "tsx test-api-client.ts",
|
|
36
|
-
"prepublishOnly": "npm run build:prod"
|
|
36
|
+
"prepublishOnly": "rm -rf dist && npm run build:prod"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^24.3.1",
|