Skip to content

Commit be53a3e

Browse files
authored
Merge pull request #297 from XpressAI/fahreza/display-remote-lib
✨ Display Remote Component Libraries
2 parents c01a73c + 4e3abf7 commit be53a3e

File tree

13 files changed

+389
-154
lines changed

13 files changed

+389
-154
lines changed

binder/postBuild

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
find xai_components/ -mindepth 1 -maxdepth 1 -type d ! -name 'xai_controlflow' ! -name 'xai_events' ! -name 'xai_template' ! -name 'xai_utils' -exec rm -rf {} +
12
python -m pip install -U pip
23
pip install -e .
34
jupyter labextension develop . --overwrite
45
jupyter server extension enable xircuits
5-
xircuits list
6+
xircuits list

src/context-menu/TrayContextMenu.tsx

Lines changed: 119 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,147 @@
1-
import React, { useEffect, useRef } from 'react';
1+
import React, { useEffect, useState, useRef } from 'react';
22
import ReactDOM from 'react-dom';
33
import { requestAPI } from '../server/handler';
44
import { commandIDs } from '../components/XircuitsBodyWidget';
55
import { startRunOutputStr } from '../components/runner/RunOutput';
66
import '../../style/ContextMenu.css';
7+
import { buildLocalFilePath, fetchLibraryConfig } from '../tray_library/ComponentLibraryConfig';
78

89
export interface TrayContextMenuProps {
9-
app: any; // Specify the correct type
10+
app: any;
1011
x: number;
1112
y: number;
1213
visible: boolean;
13-
val: any; // Type this appropriately
14+
libraryName: string;
15+
status: string;
16+
refreshTrigger: () => void;
1417
onClose: () => void;
1518
}
1619

17-
async function requestLibrary(libraryName, endpoint) {
18-
const data = { libraryName };
19-
20-
try {
21-
return await requestAPI(endpoint, {
22-
body: JSON.stringify(data),
23-
method: 'POST',
24-
});
25-
} catch (reason) {
26-
console.error(`Error on POST /${endpoint}`, data, reason);
27-
}
28-
}
29-
30-
const TrayContextMenu = ({ app, x, y, visible, val, onClose }: TrayContextMenuProps) => {
31-
// Ref for the context menu
20+
const TrayContextMenu = ({ app, x, y, visible, libraryName, status, refreshTrigger, onClose }: TrayContextMenuProps) => {
3221
const trayContextMenuRef = useRef(null);
22+
const [validOptions, setValidOptions] = useState({
23+
showInFileBrowser: false,
24+
showReadme: false,
25+
showExample: false,
26+
showPageInNewTab: false
27+
});
28+
29+
useEffect(() => {
30+
// Initialize all options as invalid
31+
setValidOptions({
32+
showInFileBrowser: false,
33+
showReadme: false,
34+
showExample: false,
35+
showPageInNewTab: false
36+
});
37+
38+
const validateOptions = async () => {
39+
try {
40+
const libraryConfig = await fetchLibraryConfig(libraryName);
41+
setValidOptions({
42+
showInFileBrowser: !!libraryConfig.local_path,
43+
showReadme: await buildLocalFilePath(libraryName, 'readme') !== null,
44+
showExample: await buildLocalFilePath(libraryName, 'default_example_path') !== null,
45+
showPageInNewTab: !!libraryConfig.repository
46+
});
47+
} catch (error) {
48+
console.error('Error validating context menu options:', error);
49+
}
50+
};
51+
52+
if (visible) {
53+
validateOptions();
54+
}
55+
}, [libraryName, visible]);
3356

34-
// Function to check if a click is outside the context menu
3557
const handleClickOutside = (event) => {
3658
if (event.target.className !== "context-menu-option") {
3759
onClose();
3860
}
3961
};
4062

41-
// Effect for handling click outside
4263
useEffect(() => {
4364
document.addEventListener('click', handleClickOutside, true);
4465
return () => {
4566
document.removeEventListener('click', handleClickOutside, true);
4667
};
4768
}, []);
69+
4870
// Context menu action handlers
49-
const handleInstall = async (val) => {
50-
const userResponse = confirm("Do you want to proceed with " + val + " library installation?");
71+
const handleInstall = async (libraryName, refreshTrigger) => {
72+
const userResponse = confirm(`Do you want to proceed with ${libraryName} library installation?`);
5173
if (userResponse) {
5274
try {
53-
const response = await requestLibrary(val, "library/get_directory");
54-
if (response['path']) {
55-
let code = startRunOutputStr()
56-
code += "!pip install -r " + response['path'] + "/requirements.txt"
75+
// clone the repository
76+
const response: any = await requestAPI("library/fetch", {
77+
body: JSON.stringify({libraryName}),
78+
method: 'POST',
79+
});
80+
81+
if (response.status !== 'OK') {
82+
throw new Error(response.message || 'Failed to fetch the library.');
83+
}
84+
85+
const libraryConfig = await fetchLibraryConfig(libraryName);
86+
if (libraryConfig && libraryConfig.local_path) {
87+
let code = startRunOutputStr();
88+
code += `!pip install -r ${libraryConfig.local_path}/requirements.txt`;
5789
app.commands.execute(commandIDs.executeToOutputPanel, { code });
58-
console.log(`${val} library sucessfully installed.`);
59-
} else if (response['message']) {
60-
alert(response['message']);
90+
console.log(`${libraryName} library successfully installed.`);
91+
} else {
92+
alert(`Library configuration not found for: ${libraryName}`);
6193
}
94+
refreshTrigger();
6295
} catch (error) {
63-
alert(`Failed to install ${val}: ` + error);
96+
alert(`Failed to install ${libraryName}. Please check the console for more details.`);
97+
console.error(`Failed to install ${libraryName}:`, error);
6498
}
65-
}
66-
}
67-
68-
const handleShowInFileBrowser = async (val) => {
99+
}
100+
};
101+
102+
const handleShowInFileBrowser = async (libraryName) => {
69103
try {
70-
const response = await requestLibrary(val, "library/get_directory");
71-
if (response['path']) {
72-
await app.commands.execute('filebrowser:go-to-path', { path: response['path'] });
73-
} else if (response['message']) {
74-
alert(response['message']);
104+
const libraryConfig = await fetchLibraryConfig(libraryName);
105+
106+
if (libraryConfig && libraryConfig.local_path) {
107+
await app.commands.execute('filebrowser:go-to-path', { path: libraryConfig.local_path });
75108
}
76109
} catch (error) {
77-
alert('Failed to Show in File Browser: ' + error);
110+
alert(`Failed to Show in File Browser: ${error}`);
78111
}
79112
};
80-
81-
const handleShowReadme = async (val) => {
113+
114+
const handleShowReadme = async (libraryName) => {
115+
try {
116+
const readmePath = await buildLocalFilePath(libraryName, 'readme');
117+
if (readmePath) {
118+
await app.commands.execute('markdownviewer:open', { path: readmePath, options: { mode: 'split-right' } });
119+
}
120+
} catch (error) {
121+
alert('Failed to Show Readme: ' + error);
122+
}
123+
};
124+
125+
const handleShowExample = async (libraryName) => {
82126
try {
83-
const response = await requestLibrary(val, "library/get_readme");
84-
if (response['path']) {
85-
await app.commands.execute('markdownviewer:open', { path: response['path'], options: { mode: 'split-right'} });
86-
} else if (response['message']) {
87-
alert(response['message']);
127+
const examplePath = await buildLocalFilePath(libraryName, 'default_example_path');
128+
if (examplePath) {
129+
await app.commands.execute('docmanager:open', { path: examplePath });
88130
}
89131
} catch (error) {
90-
alert('Failed to Show Readme: ' + error);
132+
alert('Failed to Show Example: ' + error);
91133
}
92134
};
93135

94-
const handleShowExample = async (val) => {
136+
const handleShowPageInNewTab = async (libraryName) => {
95137
try {
96-
const response = await requestLibrary(val, "library/get_example");
97-
if (response['path']) {
98-
await app.commands.execute('docmanager:open', { path: response['path'] });
99-
} else if (response['message']) {
100-
alert(response['message']);
138+
const libraryConfig = await fetchLibraryConfig(libraryName);
139+
140+
if (libraryConfig && libraryConfig.repository) {
141+
window.open(libraryConfig.repository, '_blank');
101142
}
102143
} catch (error) {
103-
alert('Failed to Show Example: ' + error);
144+
alert(`Failed to Open Page: ${error}`);
104145
}
105146
};
106147

@@ -110,10 +151,29 @@ const TrayContextMenu = ({ app, x, y, visible, val, onClose }: TrayContextMenuPr
110151

111152
return ReactDOM.createPortal(
112153
<div className="context-menu" ref={trayContextMenuRef} style={{ position: 'absolute', left: `${x+5}px`, top: `${y}px`, zIndex: 1000 }}>
113-
<div className="context-menu-option" onClick={() => { handleInstall(val); onClose(); }}>Install</div>
114-
<div className="context-menu-option" onClick={() => { handleShowInFileBrowser(val); onClose(); }}>Show in File Explorer</div>
115-
<div className="context-menu-option" onClick={() => { handleShowReadme(val); onClose(); }}>See Readme</div>
116-
<div className="context-menu-option" onClick={() => { handleShowExample(val); onClose(); }}>Show Example</div>
154+
{status === 'remote' ? (
155+
<>
156+
<div className="context-menu-option" onClick={() => { handleInstall(libraryName, refreshTrigger); onClose(); }}>Install</div>
157+
{validOptions.showPageInNewTab && (
158+
<div className="context-menu-option" onClick={() => { handleShowPageInNewTab(libraryName); onClose(); }}>Open Repository</div>
159+
)}
160+
</>
161+
) : (
162+
<>
163+
{validOptions.showInFileBrowser && (
164+
<div className="context-menu-option" onClick={() => { handleShowInFileBrowser(libraryName); onClose(); }}>Show in File Explorer</div>
165+
)}
166+
{validOptions.showReadme && (
167+
<div className="context-menu-option" onClick={() => { handleShowReadme(libraryName); onClose(); }}>See Readme</div>
168+
)}
169+
{validOptions.showExample && (
170+
<div className="context-menu-option" onClick={() => { handleShowExample(libraryName); onClose(); }}>Show Example</div>
171+
)}
172+
{validOptions.showPageInNewTab && (
173+
<div className="context-menu-option" onClick={() => { handleShowPageInNewTab(libraryName); onClose(); }}>Open Repository</div>
174+
)}
175+
</>
176+
)}
117177
</div>,
118178
document.body
119179
);

src/tray_library/AdvanceComponentLib.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ interface AdvancedComponentLibraryProps {
88
export async function fetchNodeByName(name?: string) {
99
let componentList: string[] = [];
1010

11-
// get the component list
12-
const response_1 = await ComponentList();
13-
componentList = response_1;
11+
componentList = await ComponentList();
1412

1513
let component_task = componentList.map(x => x["task"]);
1614
let drop_node = component_task.indexOf(name);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { requestAPI } from "../server/handler";
2+
3+
let libraryConfigCache = {
4+
data: null
5+
};
6+
7+
export async function fetchComponentLibraryConfig() {
8+
try {
9+
const response = await requestAPI<any>('library/get_config');
10+
if (response.status === 'OK' && response.config) {
11+
return response.config.libraries;
12+
} else {
13+
console.error('Failed to fetch remote libraries due to unexpected response:', response);
14+
return [];
15+
}
16+
} catch (error) {
17+
console.error('Failed to fetch remote libraries:', error);
18+
return [];
19+
}
20+
}
21+
22+
export async function reloadComponentLibraryConfig() {
23+
24+
try {
25+
await requestAPI('library/reload_config', {
26+
method: 'POST',
27+
headers: {
28+
'Content-Type': 'application/json'
29+
}
30+
});
31+
} catch (error) {
32+
console.error('Failed to reload config: ', error);
33+
}
34+
}
35+
36+
export async function ComponentLibraryConfig() {
37+
38+
if (!libraryConfigCache.data) {
39+
libraryConfigCache.data = await fetchComponentLibraryConfig();
40+
}
41+
42+
return libraryConfigCache.data;
43+
}
44+
45+
export async function refreshComponentLibraryConfigCache() {
46+
await reloadComponentLibraryConfig();
47+
libraryConfigCache.data = await fetchComponentLibraryConfig();
48+
}
49+
50+
export const fetchLibraryConfig = async (libName) => {
51+
try {
52+
let config = await ComponentLibraryConfig();
53+
const libraryConfig = config.find(library => library.library_id === libName.toUpperCase());
54+
55+
if (!libraryConfig) {
56+
// console.log(`Library not found for: ${libName}`);
57+
return null;
58+
}
59+
60+
return libraryConfig;
61+
} catch (error) {
62+
// console.log(`Failed to fetch library configuration: ${error}`);
63+
return null;
64+
}
65+
};
66+
67+
export const buildLocalFilePath = async (libName, fileKey) => {
68+
const libraryConfig = await fetchLibraryConfig(libName);
69+
70+
if (libraryConfig && libraryConfig[fileKey]) {
71+
return `${libraryConfig.local_path}/${libraryConfig[fileKey]}`;
72+
} else if (libraryConfig) {
73+
// console.log(`File not found for: ${libName} (Key: ${fileKey})`);
74+
}
75+
76+
return null;
77+
};

0 commit comments

Comments
 (0)