diff --git a/binder/postBuild b/binder/postBuild index 6981ad97..1b67e4c6 100644 --- a/binder/postBuild +++ b/binder/postBuild @@ -1,5 +1,6 @@ +find xai_components/ -mindepth 1 -maxdepth 1 -type d ! -name 'xai_controlflow' ! -name 'xai_events' ! -name 'xai_template' ! -name 'xai_utils' -exec rm -rf {} + python -m pip install -U pip pip install -e . jupyter labextension develop . --overwrite jupyter server extension enable xircuits -xircuits list \ No newline at end of file +xircuits list diff --git a/src/context-menu/TrayContextMenu.tsx b/src/context-menu/TrayContextMenu.tsx index 22eec809..7a185d70 100644 --- a/src/context-menu/TrayContextMenu.tsx +++ b/src/context-menu/TrayContextMenu.tsx @@ -1,106 +1,147 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import ReactDOM from 'react-dom'; import { requestAPI } from '../server/handler'; import { commandIDs } from '../components/XircuitsBodyWidget'; import { startRunOutputStr } from '../components/runner/RunOutput'; import '../../style/ContextMenu.css'; +import { buildLocalFilePath, fetchLibraryConfig } from '../tray_library/ComponentLibraryConfig'; export interface TrayContextMenuProps { - app: any; // Specify the correct type + app: any; x: number; y: number; visible: boolean; - val: any; // Type this appropriately + libraryName: string; + status: string; + refreshTrigger: () => void; onClose: () => void; } -async function requestLibrary(libraryName, endpoint) { - const data = { libraryName }; - - try { - return await requestAPI(endpoint, { - body: JSON.stringify(data), - method: 'POST', - }); - } catch (reason) { - console.error(`Error on POST /${endpoint}`, data, reason); - } -} - -const TrayContextMenu = ({ app, x, y, visible, val, onClose }: TrayContextMenuProps) => { - // Ref for the context menu +const TrayContextMenu = ({ app, x, y, visible, libraryName, status, refreshTrigger, onClose }: TrayContextMenuProps) => { const trayContextMenuRef = useRef(null); + const [validOptions, setValidOptions] = useState({ + showInFileBrowser: false, + showReadme: false, + showExample: false, + showPageInNewTab: false + }); + + useEffect(() => { + // Initialize all options as invalid + setValidOptions({ + showInFileBrowser: false, + showReadme: false, + showExample: false, + showPageInNewTab: false + }); + + const validateOptions = async () => { + try { + const libraryConfig = await fetchLibraryConfig(libraryName); + setValidOptions({ + showInFileBrowser: !!libraryConfig.local_path, + showReadme: await buildLocalFilePath(libraryName, 'readme') !== null, + showExample: await buildLocalFilePath(libraryName, 'default_example_path') !== null, + showPageInNewTab: !!libraryConfig.repository + }); + } catch (error) { + console.error('Error validating context menu options:', error); + } + }; + + if (visible) { + validateOptions(); + } + }, [libraryName, visible]); - // Function to check if a click is outside the context menu const handleClickOutside = (event) => { if (event.target.className !== "context-menu-option") { onClose(); } }; - // Effect for handling click outside useEffect(() => { document.addEventListener('click', handleClickOutside, true); return () => { document.removeEventListener('click', handleClickOutside, true); }; }, []); + // Context menu action handlers - const handleInstall = async (val) => { - const userResponse = confirm("Do you want to proceed with " + val + " library installation?"); + const handleInstall = async (libraryName, refreshTrigger) => { + const userResponse = confirm(`Do you want to proceed with ${libraryName} library installation?`); if (userResponse) { try { - const response = await requestLibrary(val, "library/get_directory"); - if (response['path']) { - let code = startRunOutputStr() - code += "!pip install -r " + response['path'] + "/requirements.txt" + // clone the repository + const response: any = await requestAPI("library/fetch", { + body: JSON.stringify({libraryName}), + method: 'POST', + }); + + if (response.status !== 'OK') { + throw new Error(response.message || 'Failed to fetch the library.'); + } + + const libraryConfig = await fetchLibraryConfig(libraryName); + if (libraryConfig && libraryConfig.local_path) { + let code = startRunOutputStr(); + code += `!pip install -r ${libraryConfig.local_path}/requirements.txt`; app.commands.execute(commandIDs.executeToOutputPanel, { code }); - console.log(`${val} library sucessfully installed.`); - } else if (response['message']) { - alert(response['message']); + console.log(`${libraryName} library successfully installed.`); + } else { + alert(`Library configuration not found for: ${libraryName}`); } + refreshTrigger(); } catch (error) { - alert(`Failed to install ${val}: ` + error); + alert(`Failed to install ${libraryName}. Please check the console for more details.`); + console.error(`Failed to install ${libraryName}:`, error); } - } - } - - const handleShowInFileBrowser = async (val) => { + } + }; + + const handleShowInFileBrowser = async (libraryName) => { try { - const response = await requestLibrary(val, "library/get_directory"); - if (response['path']) { - await app.commands.execute('filebrowser:go-to-path', { path: response['path'] }); - } else if (response['message']) { - alert(response['message']); + const libraryConfig = await fetchLibraryConfig(libraryName); + + if (libraryConfig && libraryConfig.local_path) { + await app.commands.execute('filebrowser:go-to-path', { path: libraryConfig.local_path }); } } catch (error) { - alert('Failed to Show in File Browser: ' + error); + alert(`Failed to Show in File Browser: ${error}`); } }; - - const handleShowReadme = async (val) => { + + const handleShowReadme = async (libraryName) => { + try { + const readmePath = await buildLocalFilePath(libraryName, 'readme'); + if (readmePath) { + await app.commands.execute('markdownviewer:open', { path: readmePath, options: { mode: 'split-right' } }); + } + } catch (error) { + alert('Failed to Show Readme: ' + error); + } + }; + + const handleShowExample = async (libraryName) => { try { - const response = await requestLibrary(val, "library/get_readme"); - if (response['path']) { - await app.commands.execute('markdownviewer:open', { path: response['path'], options: { mode: 'split-right'} }); - } else if (response['message']) { - alert(response['message']); + const examplePath = await buildLocalFilePath(libraryName, 'default_example_path'); + if (examplePath) { + await app.commands.execute('docmanager:open', { path: examplePath }); } } catch (error) { - alert('Failed to Show Readme: ' + error); + alert('Failed to Show Example: ' + error); } }; - const handleShowExample = async (val) => { + const handleShowPageInNewTab = async (libraryName) => { try { - const response = await requestLibrary(val, "library/get_example"); - if (response['path']) { - await app.commands.execute('docmanager:open', { path: response['path'] }); - } else if (response['message']) { - alert(response['message']); + const libraryConfig = await fetchLibraryConfig(libraryName); + + if (libraryConfig && libraryConfig.repository) { + window.open(libraryConfig.repository, '_blank'); } } catch (error) { - alert('Failed to Show Example: ' + error); + alert(`Failed to Open Page: ${error}`); } }; @@ -110,10 +151,29 @@ const TrayContextMenu = ({ app, x, y, visible, val, onClose }: TrayContextMenuPr return ReactDOM.createPortal(
-
{ handleInstall(val); onClose(); }}>Install
-
{ handleShowInFileBrowser(val); onClose(); }}>Show in File Explorer
-
{ handleShowReadme(val); onClose(); }}>See Readme
-
{ handleShowExample(val); onClose(); }}>Show Example
+ {status === 'remote' ? ( + <> +
{ handleInstall(libraryName, refreshTrigger); onClose(); }}>Install
+ {validOptions.showPageInNewTab && ( +
{ handleShowPageInNewTab(libraryName); onClose(); }}>Open Repository
+ )} + + ) : ( + <> + {validOptions.showInFileBrowser && ( +
{ handleShowInFileBrowser(libraryName); onClose(); }}>Show in File Explorer
+ )} + {validOptions.showReadme && ( +
{ handleShowReadme(libraryName); onClose(); }}>See Readme
+ )} + {validOptions.showExample && ( +
{ handleShowExample(libraryName); onClose(); }}>Show Example
+ )} + {validOptions.showPageInNewTab && ( +
{ handleShowPageInNewTab(libraryName); onClose(); }}>Open Repository
+ )} + + )}
, document.body ); diff --git a/src/tray_library/AdvanceComponentLib.tsx b/src/tray_library/AdvanceComponentLib.tsx index 5013e8ec..665e787a 100644 --- a/src/tray_library/AdvanceComponentLib.tsx +++ b/src/tray_library/AdvanceComponentLib.tsx @@ -8,9 +8,7 @@ interface AdvancedComponentLibraryProps { export async function fetchNodeByName(name?: string) { let componentList: string[] = []; - // get the component list - const response_1 = await ComponentList(); - componentList = response_1; + componentList = await ComponentList(); let component_task = componentList.map(x => x["task"]); let drop_node = component_task.indexOf(name); diff --git a/src/tray_library/ComponentLibraryConfig.tsx b/src/tray_library/ComponentLibraryConfig.tsx new file mode 100644 index 00000000..f42029fa --- /dev/null +++ b/src/tray_library/ComponentLibraryConfig.tsx @@ -0,0 +1,77 @@ +import { requestAPI } from "../server/handler"; + +let libraryConfigCache = { + data: null +}; + +export async function fetchComponentLibraryConfig() { + try { + const response = await requestAPI('library/get_config'); + if (response.status === 'OK' && response.config) { + return response.config.libraries; + } else { + console.error('Failed to fetch remote libraries due to unexpected response:', response); + return []; + } + } catch (error) { + console.error('Failed to fetch remote libraries:', error); + return []; + } +} + +export async function reloadComponentLibraryConfig() { + + try { + await requestAPI('library/reload_config', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + } catch (error) { + console.error('Failed to reload config: ', error); + } +} + +export async function ComponentLibraryConfig() { + + if (!libraryConfigCache.data) { + libraryConfigCache.data = await fetchComponentLibraryConfig(); + } + + return libraryConfigCache.data; +} + +export async function refreshComponentLibraryConfigCache() { + await reloadComponentLibraryConfig(); + libraryConfigCache.data = await fetchComponentLibraryConfig(); +} + +export const fetchLibraryConfig = async (libName) => { + try { + let config = await ComponentLibraryConfig(); + const libraryConfig = config.find(library => library.library_id === libName.toUpperCase()); + + if (!libraryConfig) { + // console.log(`Library not found for: ${libName}`); + return null; + } + + return libraryConfig; + } catch (error) { + // console.log(`Failed to fetch library configuration: ${error}`); + return null; + } +}; + +export const buildLocalFilePath = async (libName, fileKey) => { + const libraryConfig = await fetchLibraryConfig(libName); + + if (libraryConfig && libraryConfig[fileKey]) { + return `${libraryConfig.local_path}/${libraryConfig[fileKey]}`; + } else if (libraryConfig) { + // console.log(`File not found for: ${libName} (Key: ${fileKey})`); + } + + return null; +}; diff --git a/src/tray_library/Sidebar.tsx b/src/tray_library/Sidebar.tsx index e976bf1f..0d890d3e 100644 --- a/src/tray_library/Sidebar.tsx +++ b/src/tray_library/Sidebar.tsx @@ -4,7 +4,6 @@ import styled from '@emotion/styled'; import { TrayItemWidget } from './TrayItemWidget'; import { TrayWidget } from './TrayWidget'; import { JupyterFrontEnd } from '@jupyterlab/application'; -import { requestAPI } from '../server/handler'; import { Accordion, @@ -18,6 +17,7 @@ import { XircuitsFactory } from '../XircuitsFactory'; import TrayContextMenu from '../context-menu/TrayContextMenu'; import '../../style/ContextMenu.css' +import { ComponentLibraryConfig, refreshComponentLibraryConfigCache } from './ComponentLibraryConfig'; export const Body = styled.div` flex-grow: 1; @@ -88,6 +88,7 @@ export default function Sidebar(props: SidebarProps) { const [componentList, setComponentList] = React.useState([]); const [category, setCategory] = React.useState([]); + const [remoteLibList, setRemoteLibList] = React.useState([]); const [searchTerm, setSearchTerm] = useState(''); const [runOnce, setRunOnce] = useState(false); @@ -103,12 +104,7 @@ export default function Sidebar(props: SidebarProps) { const fetchComponentList = async () => { - try { - // Trigger the load library config on refresh - await requestAPI('library/reload_config', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); - } catch (error){ - console.error('Failed to reload config: ', error); - } + await refreshComponentLibraryConfigCache(); // get the component list const component_list = await ComponentList(); @@ -124,6 +120,10 @@ export default function Sidebar(props: SidebarProps) { setComponentList(component_list); setCategory(component_library_name); + + const libraryConfig = await ComponentLibraryConfig(); + const remoteLibraries = libraryConfig.filter(library => library.status === "remote"); + setRemoteLibList(remoteLibraries); } useEffect(() => { @@ -135,7 +135,7 @@ export default function Sidebar(props: SidebarProps) { }, [category, componentList]); function handleRefreshOnClick() { - refreshComponentListCache() + refreshComponentListCache(); fetchComponentList(); } @@ -183,33 +183,55 @@ export default function Sidebar(props: SidebarProps) { // Function to map categories const mapCategories = (categories, components) => { - return categories.map((val, i) => ( + return categories.map((libraryName, i) => ( - handleRightClick(e, val["task"])}>{val["task"]} + handleRightClick(event, libraryName["task"], 'installed')}>{libraryName["task"]} - {mapComponents(components.filter(component => component["category"].toString().toUpperCase() === val["task"].toString()), "")} + {mapComponents(components.filter(component => component["category"].toString().toUpperCase() === libraryName["task"].toString()), "")} )); } + + const mapRemoteLibraries = () => { + const sortedRemoteLibList = remoteLibList.sort((a, b) => a.library_id.localeCompare(b.library_id)); + + return sortedRemoteLibList.map((lib, i) => ( + + + handleRightClick(event, lib.library_id, 'remote')}>{lib.library_id} + + + )); + }; const [contextMenuState, setContextMenuState] = useState({ visible: false, x: 0, y: 0, - val: null + libraryName: null, + status: 'installed' }); - const handleRightClick = (e, val) => { + const handleRightClick = (e, libraryName, status) => { e.preventDefault(); + + // Prevent context menu from appearing for GENERAL component library + if (libraryName === 'GENERAL') { + return; + } + const rect = e.target.getBoundingClientRect(); setContextMenuState({ visible: true, x: rect.right, y: rect.top, - val: val + libraryName: libraryName, + status: status }); }; @@ -223,15 +245,22 @@ export default function Sidebar(props: SidebarProps) {
- + -
- +
{searchTerm === "" ? ( - - {mapCategories(category, componentList)} - + <> + + {mapCategories(category, componentList)} + + +
+
AVAILABLE FOR INSTALLATION
+ + {mapRemoteLibraries()} + + ) : ( mapComponents(componentList, searchTerm) )} @@ -243,9 +272,11 @@ export default function Sidebar(props: SidebarProps) { x={contextMenuState.x} y={contextMenuState.y} visible={contextMenuState.visible} - val={contextMenuState.val} + libraryName={contextMenuState.libraryName} + status={contextMenuState.status} + refreshTrigger={handleRefreshOnClick} onClose={closeContextMenu} /> ) -}; +}; \ No newline at end of file diff --git a/style/ContextMenu.css b/style/ContextMenu.css index cadd34d1..b5ea5d16 100644 --- a/style/ContextMenu.css +++ b/style/ContextMenu.css @@ -19,6 +19,7 @@ flex-grow: 1; width: 90%; height: 19px; + min-width: 100px; font-size:small; padding: 5px 7px; color: #fff; diff --git a/style/Sidebar.css b/style/Sidebar.css index 4454cc17..9a1fdfd1 100644 --- a/style/Sidebar.css +++ b/style/Sidebar.css @@ -53,6 +53,18 @@ animation: fadein 0.45s ease-in; } + .accordion__button--remote { + background-color: #fff; + color: rgb(30, 69, 131); + outline: 2px solid rgb(30, 69, 131); + outline-offset: -2px; + } + + .accordion__button--remote:hover { + background-color: rgba(255, 255, 255, 0.8); + outline-color: rgba(30, 69, 131, 0.8); + } + /* -------------------------------------------------- */ /* ---------------- Animation part ------------------ */ /* -------------------------------------------------- */ diff --git a/xircuits/_version.py b/xircuits/_version.py index 4eb6efbd..01612dc9 100644 --- a/xircuits/_version.py +++ b/xircuits/_version.py @@ -1,4 +1,4 @@ # This file is auto-generated by Hatchling. As such, do not: # - modify # - track in version control e.g. be sure to add to .gitignore -__version__ = VERSION = '1.9.1' +__version__ = VERSION = '1.10.2' \ No newline at end of file diff --git a/xircuits/handlers/__init__.py b/xircuits/handlers/__init__.py index 6bf9e843..bd7c14bf 100644 --- a/xircuits/handlers/__init__.py +++ b/xircuits/handlers/__init__.py @@ -5,7 +5,7 @@ from .config import RunConfigRouteHandler, SplitModeConfigHandler from .debugger import DebuggerRouteHandler from .spark_submit import SparkSubmitRouteHandler -from .request_library import InstallLibraryRouteHandler, GetLibraryDirectoryRouteHandler, GetLibraryReadmeRouteHandler, GetLibraryExampleRouteHandler, ReloadComponentLibraryConfigHandler +from .request_library import InstallLibraryRouteHandler, FetchLibraryRouteHandler, GetLibraryDirectoryRouteHandler, GetLibraryReadmeRouteHandler, GetLibraryExampleRouteHandler, ReloadComponentLibraryConfigHandler, GetComponentLibraryConfigHandler def setup_handlers(web_app, url_path): @@ -42,6 +42,14 @@ def setup_handlers(web_app, url_path): url_path_join(base_url, url_path, "library/reload_config"), ReloadComponentLibraryConfigHandler ), + ( + url_path_join(base_url, url_path, "library/get_config"), + GetComponentLibraryConfigHandler + ), + ( + url_path_join(base_url, url_path, "library/fetch"), + FetchLibraryRouteHandler + ), ( url_path_join(base_url, url_path, "library/install"), InstallLibraryRouteHandler diff --git a/xircuits/handlers/request_library.py b/xircuits/handlers/request_library.py index 672bd2ec..5d1ce7ee 100644 --- a/xircuits/handlers/request_library.py +++ b/xircuits/handlers/request_library.py @@ -3,8 +3,9 @@ import traceback import tornado import posixpath +from http import HTTPStatus from jupyter_server.base.handlers import APIHandler -from xircuits.library import install_library, build_library_file_path_from_config, save_component_library_config +from xircuits.library import install_library, fetch_library, build_library_file_path_from_config, save_component_library_config, get_component_library_config class InstallLibraryRouteHandler(APIHandler): @tornado.web.authenticated @@ -13,19 +14,47 @@ def post(self): library_name = input_data.get("libraryName") if not library_name: - self.finish(json.dumps({"message": "Library name is required"})) + self.set_status(HTTPStatus.BAD_REQUEST) # Set appropriate HTTP status + self.finish(json.dumps({"error": "Library name is required"})) return try: message = install_library(library_name) + self.finish(json.dumps({"status": "OK", "message": message})) except RuntimeError as e: + self.set_status(HTTPStatus.BAD_REQUEST) message = str(e) print(message) + self.finish(json.dumps({"error": message})) except Exception as e: + self.set_status(HTTPStatus.INTERNAL_SERVER_ERROR) message = f"An unexpected error occurred: {traceback.format_exc()}" print(message) + self.finish(json.dumps({"error": message})) - self.finish(json.dumps({"message": message})) +class FetchLibraryRouteHandler(APIHandler): + @tornado.web.authenticated + def post(self): + input_data = self.get_json_body() + library_name = input_data.get("libraryName") + + if not library_name: + self.set_status(HTTPStatus.BAD_REQUEST) + self.finish(json.dumps({"error": "Library name is required"})) + return + + try: + message = fetch_library(library_name.lower()) + self.finish(json.dumps({"status": "OK", "message": message})) + except RuntimeError as e: + self.set_status(HTTPStatus.BAD_REQUEST) + message = str(e) + self.finish(json.dumps({"error": message})) + except Exception as e: + self.set_status(HTTPStatus.INTERNAL_SERVER_ERROR) + message = "An unexpected error occurred" + print(f"An unexpected error occurred: {traceback.format_exc()}") + self.finish(json.dumps({"error": message})) class GetLibraryDirectoryRouteHandler(APIHandler): @tornado.web.authenticated @@ -34,13 +63,12 @@ def post(self): library_name = input_data.get("libraryName") if not library_name: - self.finish(json.dumps({"message": "Library name is required"})) + self.set_status(HTTPStatus.BAD_REQUEST) + self.finish(json.dumps({"error": "Library name is required"})) return directory_path = posixpath.join("xai_components", f"xai_{library_name.lower()}") - - response = {"path": directory_path} - self.finish(json.dumps(response)) + self.finish(json.dumps({"status": "OK", "path": directory_path})) class GetLibraryReadmeRouteHandler(APIHandler): @tornado.web.authenticated @@ -48,15 +76,22 @@ def post(self): input_data = self.get_json_body() library_name = input_data.get("libraryName") + if not library_name: + self.set_status(HTTPStatus.BAD_REQUEST) + self.finish(json.dumps({"error": "Library name is required"})) + return + file_path = build_library_file_path_from_config(library_name, "readme") if file_path: if os.path.exists(file_path): - response = {"path": file_path} + response = {"status": "OK", "path": file_path} else: - response = {"message": "Readme file not found."} + self.set_status(HTTPStatus.NOT_FOUND) + response = {"error": "Readme file not found."} else: - response = {"message": "Readme configuration not found."} + self.set_status(HTTPStatus.NOT_FOUND) + response = {"error": "Readme configuration not found."} self.finish(json.dumps(response)) @@ -66,15 +101,22 @@ def post(self): input_data = self.get_json_body() library_name = input_data.get("libraryName") + if not library_name: + self.set_status(HTTPStatus.BAD_REQUEST) + self.finish(json.dumps({"error": "Library name is required"})) + return + example_path = build_library_file_path_from_config(library_name, "default_example_path") if example_path: if os.path.exists(example_path): - response = {"path": example_path} + response = {"status": "OK", "path": example_path} else: - response = {"message": "Example file not found."} + self.set_status(HTTPStatus.NOT_FOUND) + response = {"error": "Example file not found."} else: - response = {"message": "Example configuration not found."} + self.set_status(HTTPStatus.NOT_FOUND) + response = {"error": "Example configuration not found."} self.finish(json.dumps(response)) @@ -83,8 +125,24 @@ class ReloadComponentLibraryConfigHandler(APIHandler): def post(self): try: save_component_library_config() - response = {"message": "Library config updated."} + response = {"status": "OK", "message": "Library config updated."} + except Exception as e: + self.set_status(HTTPStatus.INTERNAL_SERVER_ERROR) + response = {"error": f"Something went wrong: {str(e)}"} + print(f"An error occurred: {traceback.format_exc()}") + + self.finish(json.dumps(response)) + + +class GetComponentLibraryConfigHandler(APIHandler): + @tornado.web.authenticated + def get(self): + try: + config = get_component_library_config() + response = {"status": "OK", "config": config} except Exception as e: - response = {"message": f"Something went wrong: {str(e)}"} + self.set_status(HTTPStatus.INTERNAL_SERVER_ERROR) + response = {"error": f"Something went wrong: {str(e)}"} + print(f"An error occurred: {traceback.format_exc()}") - self.finish(json.dumps(response)) \ No newline at end of file + self.finish(json.dumps(response)) diff --git a/xircuits/library/__init__.py b/xircuits/library/__init__.py index cea962cd..029f3725 100644 --- a/xircuits/library/__init__.py +++ b/xircuits/library/__init__.py @@ -1,3 +1,3 @@ from .list_library import list_component_library from .install_fetch_library import install_library, fetch_library, get_library_config, build_library_file_path_from_config -from .generate_component_library_config import save_component_library_config \ No newline at end of file +from .generate_component_library_config import save_component_library_config, get_component_library_config \ No newline at end of file diff --git a/xircuits/library/generate_component_library_config.py b/xircuits/library/generate_component_library_config.py index 72b5bb30..accfbcd0 100644 --- a/xircuits/library/generate_component_library_config.py +++ b/xircuits/library/generate_component_library_config.py @@ -35,50 +35,39 @@ def read_file_lines_to_list(file_path): with open(file_path, 'r') as file: return [line.strip() for line in file.readlines()] -def extract_library_info(lib_path, base_path, status="installed"): - relative_lib_path = os.path.join(base_path, os.path.relpath(lib_path, start=base_path)) - toml_path = os.path.join(lib_path, 'pyproject.toml') - - if not os.path.exists(toml_path): - return None - - toml_data = parse_toml_file(toml_path) - - # Check if TOML data was successfully parsed - if toml_data is None: - return None - - # Remove 'xai_' or 'xai-' prefix and convert to uppercase - library_id = toml_data["project"]["name"].replace("xai_", "").replace("xai-", "").upper() +def is_installed_component(directory_path): + # Check for an __init__.py file to determine if the directory is an installed component + return os.path.isfile(os.path.join(directory_path, '__init__.py')) - requirements_rel_path = toml_data["tool"]["xircuits"].get("requirements_path", None) - requirements_path = None - requirements = [] - - if requirements_rel_path is not None: - requirements_path = os.path.join(lib_path, requirements_rel_path) - if os.path.isfile(requirements_path): - requirements = read_file_lines_to_list(requirements_path) - else: - requirements_path = None # Reset to None if the file does not exist +def extract_library_info(lib_path, base_path, status="remote"): + # If __init__.py exists, confirm the component is installed + if is_installed_component(lib_path): + status = "installed" + library_id = os.path.basename(lib_path).replace('xai_', '').replace('xai-', '').upper() lib_info = { - "name": toml_data["project"]["name"], + "name": os.path.basename(lib_path), "library_id": library_id, - "version": toml_data["project"].get("version", "N/A"), - "description": toml_data["project"].get("description", "No description available."), - "authors": toml_data["project"].get("authors", []), - "license": toml_data["project"].get("license", "N/A"), - "readme": toml_data["project"].get("readme", None), - "repository": toml_data["project"].get("repository", None), - "keywords": toml_data["project"].get("keywords", []), - "local_path": relative_lib_path, - "status": status, - "requirements_path": requirements_path, - "requirements": requirements, - "default_example_path": toml_data["tool"]["xircuits"].get("default_example_path", None), + "local_path": os.path.join(base_path, os.path.relpath(lib_path, start=base_path)), + "status": status } + toml_path = os.path.join(lib_path, 'pyproject.toml') + if os.path.exists(toml_path): + toml_data = parse_toml_file(toml_path) + if toml_data: + lib_info.update({ + "version": toml_data["project"].get("version", "N/A"), + "description": toml_data["project"].get("description", "No description available."), + "authors": toml_data["project"].get("authors", []), + "license": toml_data["project"].get("license", "N/A"), + "readme": toml_data["project"].get("readme", None), + "repository": toml_data["project"].get("repository", None), + "keywords": toml_data["project"].get("keywords", []), + "requirements": toml_data["project"].get("dependencies", []), + "default_example_path": toml_data["tool"].get("xircuits", {}).get("default_example_path", None), + }) + return lib_info def generate_component_library_config(base_path="xai_components", gitmodules_path=".gitmodules"): @@ -128,6 +117,18 @@ def save_component_library_config(filename=".xircuits/component_library_config.j os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as json_file: json.dump(libraries_data, json_file, indent=4) + return libraries_data + +def get_component_library_config(filename=".xircuits/component_library_config.json"): + if os.path.exists(filename): + try: + with open(filename, 'r') as json_file: + return json.load(json_file) + except Exception as e: + print(f"Error reading JSON file at {filename}: {e}") + return None + else: + return save_component_library_config(filename) if __name__ == "__main__": save_component_library_config() diff --git a/xircuits/library/install_fetch_library.py b/xircuits/library/install_fetch_library.py index fc572a5c..25684076 100644 --- a/xircuits/library/install_fetch_library.py +++ b/xircuits/library/install_fetch_library.py @@ -16,18 +16,6 @@ def build_component_library_path(component_library_query: str) -> str: return component_library_query -def get_library_config(library_name, config_key): - config_path = ".xircuits/component_library_config.json" - if os.path.exists(config_path): - with open(config_path, "r") as config_file: - config = json.load(config_file) - for library in config.get("libraries", []): - if library.get("library_id") == library_name: - return library.get(config_key) - - print(f"'{config_key}' not found for library '{library_name}'.") - return None - def get_library_config(library_name, config_key): config_path = ".xircuits/component_library_config.json" if os.path.exists(config_path):