Spaces:
				
			
			
	
			
			
		Build error
		
	
	
	
			
			
	
	
	
	
		
		
		Build error
		
	Find plugins by module name instead of namespace module.
Browse files- README.md +2 -3
 - {lynxkite-app/data β examples}/AIMO +0 -0
 - {lynxkite-app/data β examples}/Graph RAG +0 -0
 - {lynxkite-app/data β examples}/Image processing +0 -0
 - {lynxkite-app/data β examples}/LynxScribe demo +0 -0
 - {lynxkite-app/data β examples}/NetworkX demo +0 -0
 - {lynxkite-app/data β examples}/PyTorch demo +0 -0
 - {lynxkite-app/data β examples}/RAG chatbot app +0 -0
 - {lynxkite-app/data β examples}/aimo-examples.csv +0 -0
 - {lynxkite-app/data β examples}/example-pizza.md +0 -0
 - {lynxkite-app/data β examples}/night demo +0 -0
 - {lynxkite-app/data β examples}/sql +0 -0
 - lynxkite-app/README.md +1 -1
 - lynxkite-app/pyproject.toml +4 -4
 - lynxkite-app/src/build_frontend.py +1 -1
 - lynxkite-app/src/lynxkite/app/web_assets/__init__.py +0 -1
 - lynxkite-app/src/lynxkite/app/web_assets/assets/__init__.py +0 -1
 - lynxkite-app/src/{lynxkite/app β lynxkite_app}/__init__.py +0 -0
 - lynxkite-app/src/{lynxkite/app β lynxkite_app}/__main__.py +1 -1
 - lynxkite-app/src/{lynxkite/app β lynxkite_app}/crdt.py +0 -0
 - lynxkite-app/src/{lynxkite/app β lynxkite_app}/main.py +14 -20
 - lynxkite-app/tests/test_crdt.py +1 -1
 - lynxkite-app/tests/test_main.py +1 -1
 - lynxkite-app/web/src/Directory.tsx +43 -43
 - lynxkite-app/web/src/index.css +0 -1
 - lynxkite-core/src/lynxkite/core/ops.py +2 -0
 - lynxkite-graph-analytics/pyproject.toml +3 -3
 - lynxkite-graph-analytics/src/lynxkite_graph_analytics/__init__.py +3 -0
 - lynxkite-graph-analytics/src/{lynxkite_plugins/graph_analytics β lynxkite_graph_analytics}/lynxkite_ops.py +0 -0
 - lynxkite-graph-analytics/src/{lynxkite_plugins/graph_analytics β lynxkite_graph_analytics}/networkx_ops.py +0 -0
 - lynxkite-graph-analytics/src/{lynxkite_plugins/graph_analytics β lynxkite_graph_analytics}/pytorch_model_ops.py +0 -0
 - lynxkite-graph-analytics/src/lynxkite_plugins/graph_analytics/__init__.py +0 -3
 - lynxkite-graph-analytics/tests/test_lynxkite_ops.py +1 -1
 - lynxkite-lynxscribe/README.md +1 -1
 - lynxkite-lynxscribe/src/{lynxkite_plugins/lynxscribe β lynxkite_lynxscribe}/__init__.py +2 -2
 - lynxkite-lynxscribe/src/{lynxkite_plugins/lynxscribe β lynxkite_lynxscribe}/llm_ops.py +0 -0
 - lynxkite-lynxscribe/src/{lynxkite_plugins/lynxscribe β lynxkite_lynxscribe}/lynxscribe_ops.py +0 -0
 - lynxkite-pillow-example/pyproject.toml +2 -2
 - lynxkite-pillow-example/src/{lynxkite_plugins/pillow_example β lynxkite_pillow_example}/__init__.py +0 -0
 
    	
        README.md
    CHANGED
    
    | 
         @@ -24,14 +24,13 @@ Install everything like this: 
     | 
|
| 24 | 
         
             
            uv venv
         
     | 
| 25 | 
         
             
            source .venv/bin/activate
         
     | 
| 26 | 
         
             
            # The [dev] tag is only needed if you intend on running tests
         
     | 
| 27 | 
         
            -
            uv pip install -e lynxkite-core/[dev] lynxkite-app/[dev] lynxkite-graph-analytics/[dev] lynxkite-lynxscribe/ lynxkite-pillow-example/
         
     | 
| 28 | 
         
             
            ```
         
     | 
| 29 | 
         | 
| 30 | 
         
             
            This also builds the frontend, hopefully very quickly. To run it:
         
     | 
| 31 | 
         | 
| 32 | 
         
             
            ```bash
         
     | 
| 33 | 
         
            -
             
     | 
| 34 | 
         
            -
            LYNXKITE_RELOAD=1 lynxkite
         
     | 
| 35 | 
         
             
            ```
         
     | 
| 36 | 
         | 
| 37 | 
         
             
            If you also want to make changes to the frontend with hot reloading:
         
     | 
| 
         | 
|
| 24 | 
         
             
            uv venv
         
     | 
| 25 | 
         
             
            source .venv/bin/activate
         
     | 
| 26 | 
         
             
            # The [dev] tag is only needed if you intend on running tests
         
     | 
| 27 | 
         
            +
            uv pip install -e lynxkite-core/[dev] -e lynxkite-app/[dev] -e lynxkite-graph-analytics/[dev] -e lynxkite-lynxscribe/ -e lynxkite-pillow-example/
         
     | 
| 28 | 
         
             
            ```
         
     | 
| 29 | 
         | 
| 30 | 
         
             
            This also builds the frontend, hopefully very quickly. To run it:
         
     | 
| 31 | 
         | 
| 32 | 
         
             
            ```bash
         
     | 
| 33 | 
         
            +
            LYNXKITE_DATA=examples LYNXKITE_RELOAD=1 lynxkite
         
     | 
| 
         | 
|
| 34 | 
         
             
            ```
         
     | 
| 35 | 
         | 
| 36 | 
         
             
            If you also want to make changes to the frontend with hot reloading:
         
     | 
    	
        {lynxkite-app/data β examples}/AIMO
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/Graph RAG
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/Image processing
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/LynxScribe demo
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/NetworkX demo
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/PyTorch demo
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/RAG chatbot app
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/aimo-examples.csv
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/example-pizza.md
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/night demo
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        {lynxkite-app/data β examples}/sql
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        lynxkite-app/README.md
    CHANGED
    
    | 
         @@ -13,7 +13,7 @@ To run the backend: 
     | 
|
| 13 | 
         | 
| 14 | 
         
             
            ```bash
         
     | 
| 15 | 
         
             
            uv pip install -e .
         
     | 
| 16 | 
         
            -
            LYNXKITE_RELOAD=1 lynxkite
         
     | 
| 17 | 
         
             
            ```
         
     | 
| 18 | 
         | 
| 19 | 
         
             
            To run the frontend:
         
     | 
| 
         | 
|
| 13 | 
         | 
| 14 | 
         
             
            ```bash
         
     | 
| 15 | 
         
             
            uv pip install -e .
         
     | 
| 16 | 
         
            +
            LYNXKITE_DATA=../examples LYNXKITE_RELOAD=1 lynxkite
         
     | 
| 17 | 
         
             
            ```
         
     | 
| 18 | 
         | 
| 19 | 
         
             
            To run the frontend:
         
     | 
    	
        lynxkite-app/pyproject.toml
    CHANGED
    
    | 
         @@ -15,7 +15,7 @@ dependencies = [ 
     | 
|
| 15 | 
         | 
| 16 | 
         
             
            [project.optional-dependencies]
         
     | 
| 17 | 
         
             
            dev = [
         
     | 
| 18 | 
         
            -
                "pytest",
         
     | 
| 19 | 
         
             
            ]
         
     | 
| 20 | 
         | 
| 21 | 
         
             
            [tool.uv.sources]
         
     | 
| 
         @@ -30,8 +30,8 @@ namespaces = true 
     | 
|
| 30 | 
         
             
            where = ["src"]
         
     | 
| 31 | 
         | 
| 32 | 
         
             
            [tool.setuptools.package-data]
         
     | 
| 33 | 
         
            -
            " 
     | 
| 34 | 
         
            -
            " 
     | 
| 35 | 
         | 
| 36 | 
         
             
            [tool.setuptools]
         
     | 
| 37 | 
         
             
            py-modules = ["build_frontend"]
         
     | 
| 
         @@ -41,4 +41,4 @@ include-package-data = true 
     | 
|
| 41 | 
         
             
            build_py = "build_frontend.build_py"
         
     | 
| 42 | 
         | 
| 43 | 
         
             
            [project.scripts]
         
     | 
| 44 | 
         
            -
            lynxkite = " 
     | 
| 
         | 
|
| 15 | 
         | 
| 16 | 
         
             
            [project.optional-dependencies]
         
     | 
| 17 | 
         
             
            dev = [
         
     | 
| 18 | 
         
            +
                "pytest>=8.3.4",
         
     | 
| 19 | 
         
             
            ]
         
     | 
| 20 | 
         | 
| 21 | 
         
             
            [tool.uv.sources]
         
     | 
| 
         | 
|
| 30 | 
         
             
            where = ["src"]
         
     | 
| 31 | 
         | 
| 32 | 
         
             
            [tool.setuptools.package-data]
         
     | 
| 33 | 
         
            +
            "lynxkite_app.web_assets" = ["*"]
         
     | 
| 34 | 
         
            +
            "lynxkite_app.web_assets.assets" = ["*"]
         
     | 
| 35 | 
         | 
| 36 | 
         
             
            [tool.setuptools]
         
     | 
| 37 | 
         
             
            py-modules = ["build_frontend"]
         
     | 
| 
         | 
|
| 41 | 
         
             
            build_py = "build_frontend.build_py"
         
     | 
| 42 | 
         | 
| 43 | 
         
             
            [project.scripts]
         
     | 
| 44 | 
         
            +
            lynxkite = "lynxkite_app.__main__:main"
         
     | 
    	
        lynxkite-app/src/build_frontend.py
    CHANGED
    
    | 
         @@ -11,7 +11,7 @@ class build_py(_build_py): 
     | 
|
| 11 | 
         
             
                    print("\n\nBuilding frontend...", __file__)
         
     | 
| 12 | 
         
             
                    here = Path(__file__).parent.parent
         
     | 
| 13 | 
         
             
                    frontend_dir = here / "web"
         
     | 
| 14 | 
         
            -
                    package_dir = here / "src" / " 
     | 
| 15 | 
         
             
                    subprocess.check_call(["npm", "install"], cwd=frontend_dir)
         
     | 
| 16 | 
         
             
                    subprocess.check_call(["npm", "run", "build"], cwd=frontend_dir)
         
     | 
| 17 | 
         
             
                    print("files in", frontend_dir / "dist")
         
     | 
| 
         | 
|
| 11 | 
         
             
                    print("\n\nBuilding frontend...", __file__)
         
     | 
| 12 | 
         
             
                    here = Path(__file__).parent.parent
         
     | 
| 13 | 
         
             
                    frontend_dir = here / "web"
         
     | 
| 14 | 
         
            +
                    package_dir = here / "src" / "lynxkite_app" / "web_assets"
         
     | 
| 15 | 
         
             
                    subprocess.check_call(["npm", "install"], cwd=frontend_dir)
         
     | 
| 16 | 
         
             
                    subprocess.check_call(["npm", "run", "build"], cwd=frontend_dir)
         
     | 
| 17 | 
         
             
                    print("files in", frontend_dir / "dist")
         
     | 
    	
        lynxkite-app/src/lynxkite/app/web_assets/__init__.py
    DELETED
    
    | 
         @@ -1 +0,0 @@ 
     | 
|
| 1 | 
         
            -
            """The build process (uv build) puts the frontend build artifacts here."""
         
     | 
| 
         | 
|
| 
         | 
    	
        lynxkite-app/src/lynxkite/app/web_assets/assets/__init__.py
    DELETED
    
    | 
         @@ -1 +0,0 @@ 
     | 
|
| 1 | 
         
            -
            """More frontend build artifacts."""
         
     | 
| 
         | 
|
| 
         | 
    	
        lynxkite-app/src/{lynxkite/app β lynxkite_app}/__init__.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        lynxkite-app/src/{lynxkite/app β lynxkite_app}/__main__.py
    RENAMED
    
    | 
         @@ -6,7 +6,7 @@ import os 
     | 
|
| 6 | 
         
             
            def main():
         
     | 
| 7 | 
         
             
                port = int(os.environ.get("PORT", "8000"))
         
     | 
| 8 | 
         
             
                reload = bool(os.environ.get("LYNXKITE_RELOAD", ""))
         
     | 
| 9 | 
         
            -
                uvicorn.run(" 
     | 
| 10 | 
         | 
| 11 | 
         | 
| 12 | 
         
             
            if __name__ == "__main__":
         
     | 
| 
         | 
|
| 6 | 
         
             
            def main():
         
     | 
| 7 | 
         
             
                port = int(os.environ.get("PORT", "8000"))
         
     | 
| 8 | 
         
             
                reload = bool(os.environ.get("LYNXKITE_RELOAD", ""))
         
     | 
| 9 | 
         
            +
                uvicorn.run("lynxkite_app.main:app", host="0.0.0.0", port=port, reload=reload)
         
     | 
| 10 | 
         | 
| 11 | 
         | 
| 12 | 
         
             
            if __name__ == "__main__":
         
     | 
    	
        lynxkite-app/src/{lynxkite/app β lynxkite_app}/crdt.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        lynxkite-app/src/{lynxkite/app β lynxkite_app}/main.py
    RENAMED
    
    | 
         @@ -1,5 +1,8 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 1 | 
         
             
            import os
         
     | 
| 2 | 
         
             
            import shutil
         
     | 
| 
         | 
|
| 3 | 
         
             
            if os.environ.get("NX_CUGRAPH_AUTOCONFIG", "").strip().lower() == "true":
         
     | 
| 4 | 
         
             
                import cudf.pandas
         
     | 
| 5 | 
         | 
| 
         @@ -17,19 +20,13 @@ from . import crdt 
     | 
|
| 17 | 
         | 
| 18 | 
         | 
| 19 | 
         
             
            def detect_plugins():
         
     | 
| 20 | 
         
            -
                try:
         
     | 
| 21 | 
         
            -
                    import lynxkite_plugins
         
     | 
| 22 | 
         
            -
                except ImportError:
         
     | 
| 23 | 
         
            -
                    print("No modules found in lynxkite_plugins. Be sure to install some plugins.")
         
     | 
| 24 | 
         
            -
                    return {}
         
     | 
| 25 | 
         
            -
             
     | 
| 26 | 
         
             
                plugins = {}
         
     | 
| 27 | 
         
            -
                for _, name, _ in pkgutil.iter_modules( 
     | 
| 28 | 
         
            -
                    name 
     | 
| 29 | 
         
            -
             
     | 
| 30 | 
         
            -
             
     | 
| 31 | 
         
             
                if not plugins:
         
     | 
| 32 | 
         
            -
                    print("No  
     | 
| 33 | 
         
             
                return plugins
         
     | 
| 34 | 
         | 
| 35 | 
         | 
| 
         @@ -69,7 +66,7 @@ async def save_and_execute(req: SaveRequest): 
     | 
|
| 69 | 
         
             
            @app.post("/api/delete")
         
     | 
| 70 | 
         
             
            async def delete_workspace(req: dict):
         
     | 
| 71 | 
         
             
                json_path: pathlib.Path = DATA_PATH / req["path"]
         
     | 
| 72 | 
         
            -
                crdt_path: pathlib.Path = CRDT_PATH / f"{req[ 
     | 
| 73 | 
         
             
                assert json_path.is_relative_to(DATA_PATH)
         
     | 
| 74 | 
         
             
                assert crdt_path.is_relative_to(CRDT_PATH)
         
     | 
| 75 | 
         
             
                json_path.unlink()
         
     | 
| 
         @@ -85,8 +82,9 @@ def load(path: str): 
     | 
|
| 85 | 
         
             
                return workspace.load(path)
         
     | 
| 86 | 
         | 
| 87 | 
         | 
| 88 | 
         
            -
            DATA_PATH = pathlib.Path. 
     | 
| 89 | 
         
            -
            CRDT_PATH = pathlib.Path. 
     | 
| 
         | 
|
| 90 | 
         | 
| 91 | 
         
             
            @dataclasses.dataclass(order=True)
         
     | 
| 92 | 
         
             
            class DirectoryEntry:
         
     | 
| 
         @@ -120,11 +118,7 @@ def make_dir(req: dict): 
     | 
|
| 120 | 
         
             
            @app.post("/api/dir/delete")
         
     | 
| 121 | 
         
             
            def delete_dir(req: dict):
         
     | 
| 122 | 
         
             
                path: pathlib.Path = DATA_PATH / req["path"]
         
     | 
| 123 | 
         
            -
                assert all([
         
     | 
| 124 | 
         
            -
                    path.is_relative_to(DATA_PATH),
         
     | 
| 125 | 
         
            -
                    path.exists(),
         
     | 
| 126 | 
         
            -
                    path.is_dir()
         
     | 
| 127 | 
         
            -
                ])
         
     | 
| 128 | 
         
             
                shutil.rmtree(path)
         
     | 
| 129 | 
         
             
                return list_dir(path.parent)
         
     | 
| 130 | 
         | 
| 
         @@ -159,5 +153,5 @@ class SPAStaticFiles(StaticFiles): 
     | 
|
| 159 | 
         
             
                            raise ex
         
     | 
| 160 | 
         | 
| 161 | 
         | 
| 162 | 
         
            -
            static_dir = SPAStaticFiles(packages=[(" 
     | 
| 163 | 
         
             
            app.mount("/", static_dir, name="web_assets")
         
     | 
| 
         | 
|
| 1 | 
         
            +
            """The FastAPI server for serving the LynxKite application."""
         
     | 
| 2 | 
         
            +
             
     | 
| 3 | 
         
             
            import os
         
     | 
| 4 | 
         
             
            import shutil
         
     | 
| 5 | 
         
            +
             
     | 
| 6 | 
         
             
            if os.environ.get("NX_CUGRAPH_AUTOCONFIG", "").strip().lower() == "true":
         
     | 
| 7 | 
         
             
                import cudf.pandas
         
     | 
| 8 | 
         | 
| 
         | 
|
| 20 | 
         | 
| 21 | 
         | 
| 22 | 
         
             
            def detect_plugins():
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 23 | 
         
             
                plugins = {}
         
     | 
| 24 | 
         
            +
                for _, name, _ in pkgutil.iter_modules():
         
     | 
| 25 | 
         
            +
                    if name.startswith("lynxkite_"):
         
     | 
| 26 | 
         
            +
                        print(f"Importing {name}")
         
     | 
| 27 | 
         
            +
                        plugins[name] = importlib.import_module(name)
         
     | 
| 28 | 
         
             
                if not plugins:
         
     | 
| 29 | 
         
            +
                    print("No LynxKite plugins found. Be sure to install some!")
         
     | 
| 30 | 
         
             
                return plugins
         
     | 
| 31 | 
         | 
| 32 | 
         | 
| 
         | 
|
| 66 | 
         
             
            @app.post("/api/delete")
         
     | 
| 67 | 
         
             
            async def delete_workspace(req: dict):
         
     | 
| 68 | 
         
             
                json_path: pathlib.Path = DATA_PATH / req["path"]
         
     | 
| 69 | 
         
            +
                crdt_path: pathlib.Path = CRDT_PATH / f"{req['path']}.crdt"
         
     | 
| 70 | 
         
             
                assert json_path.is_relative_to(DATA_PATH)
         
     | 
| 71 | 
         
             
                assert crdt_path.is_relative_to(CRDT_PATH)
         
     | 
| 72 | 
         
             
                json_path.unlink()
         
     | 
| 
         | 
|
| 82 | 
         
             
                return workspace.load(path)
         
     | 
| 83 | 
         | 
| 84 | 
         | 
| 85 | 
         
            +
            DATA_PATH = pathlib.Path(os.environ.get("LYNXKITE_DATA", "lynxkite_data"))
         
     | 
| 86 | 
         
            +
            CRDT_PATH = pathlib.Path(os.environ.get("LYNXKITE_CRDT_DATA", "lynxkite_crdt_data"))
         
     | 
| 87 | 
         
            +
             
     | 
| 88 | 
         | 
| 89 | 
         
             
            @dataclasses.dataclass(order=True)
         
     | 
| 90 | 
         
             
            class DirectoryEntry:
         
     | 
| 
         | 
|
| 118 | 
         
             
            @app.post("/api/dir/delete")
         
     | 
| 119 | 
         
             
            def delete_dir(req: dict):
         
     | 
| 120 | 
         
             
                path: pathlib.Path = DATA_PATH / req["path"]
         
     | 
| 121 | 
         
            +
                assert all([path.is_relative_to(DATA_PATH), path.exists(), path.is_dir()])
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 122 | 
         
             
                shutil.rmtree(path)
         
     | 
| 123 | 
         
             
                return list_dir(path.parent)
         
     | 
| 124 | 
         | 
| 
         | 
|
| 153 | 
         
             
                            raise ex
         
     | 
| 154 | 
         | 
| 155 | 
         | 
| 156 | 
         
            +
            static_dir = SPAStaticFiles(packages=[("lynxkite_app", "web_assets")], html=True)
         
     | 
| 157 | 
         
             
            app.mount("/", static_dir, name="web_assets")
         
     | 
    	
        lynxkite-app/tests/test_crdt.py
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 1 | 
         
             
            from enum import Enum
         
     | 
| 2 | 
         
             
            import pycrdt
         
     | 
| 3 | 
         
             
            import pytest
         
     | 
| 4 | 
         
            -
            from  
     | 
| 5 | 
         | 
| 6 | 
         | 
| 7 | 
         
             
            @pytest.fixture
         
     | 
| 
         | 
|
| 1 | 
         
             
            from enum import Enum
         
     | 
| 2 | 
         
             
            import pycrdt
         
     | 
| 3 | 
         
             
            import pytest
         
     | 
| 4 | 
         
            +
            from lynxkite_app.crdt import crdt_update
         
     | 
| 5 | 
         | 
| 6 | 
         | 
| 7 | 
         
             
            @pytest.fixture
         
     | 
    	
        lynxkite-app/tests/test_main.py
    CHANGED
    
    | 
         @@ -1,6 +1,6 @@ 
     | 
|
| 1 | 
         
             
            import uuid
         
     | 
| 2 | 
         
             
            from fastapi.testclient import TestClient
         
     | 
| 3 | 
         
            -
            from  
     | 
| 4 | 
         
             
            import os
         
     | 
| 5 | 
         | 
| 6 | 
         | 
| 
         | 
|
| 1 | 
         
             
            import uuid
         
     | 
| 2 | 
         
             
            from fastapi.testclient import TestClient
         
     | 
| 3 | 
         
            +
            from lynxkite_app.main import app, detect_plugins, DATA_PATH
         
     | 
| 4 | 
         
             
            import os
         
     | 
| 5 | 
         | 
| 6 | 
         | 
    	
        lynxkite-app/web/src/Directory.tsx
    CHANGED
    
    | 
         @@ -29,8 +29,8 @@ export default function () { 
     | 
|
| 29 | 
         
             
              const navigate = useNavigate();
         
     | 
| 30 | 
         
             
              const [isCreatingDir, setIsCreatingDir] = useState(false);
         
     | 
| 31 | 
         
             
              const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
         
     | 
| 32 | 
         
            -
             
     | 
| 33 | 
         
            -
             
     | 
| 34 | 
         
             
              function link(item: any) {
         
     | 
| 35 | 
         
             
                if (item.type === 'directory') {
         
     | 
| 36 | 
         
             
                  return `/dir/${item.name}`;
         
     | 
| 
         @@ -38,7 +38,7 @@ export default function () { 
     | 
|
| 38 | 
         
             
                  return `/edit/${item.name}`;
         
     | 
| 39 | 
         
             
                }
         
     | 
| 40 | 
         
             
              }
         
     | 
| 41 | 
         
            -
             
     | 
| 42 | 
         
             
              function shortName(item: any) {
         
     | 
| 43 | 
         
             
                return item.name.split('/').pop();
         
     | 
| 44 | 
         
             
              }
         
     | 
| 
         @@ -53,18 +53,18 @@ export default function () { 
     | 
|
| 53 | 
         
             
                  i++;
         
     | 
| 54 | 
         
             
                }
         
     | 
| 55 | 
         
             
              }
         
     | 
| 56 | 
         
            -
             
     | 
| 57 | 
         
             
              function newWorkspaceIn(path: string, list: any[], workspaceName?: string) {
         
     | 
| 58 | 
         
             
                const pathSlash = path ? `${path}/` : "";
         
     | 
| 59 | 
         
             
                const name = workspaceName || newName(list);
         
     | 
| 60 | 
         
            -
                navigate(`/edit/${pathSlash}${name}`, {replace: true});
         
     | 
| 61 | 
         
             
              }
         
     | 
| 62 | 
         
            -
             
     | 
| 63 | 
         | 
| 64 | 
         
             
              async function newFolderIn(path: string, list: any[], folderName?: string) {
         
     | 
| 65 | 
         
             
                const name = folderName || newName(list, "New Folder");
         
     | 
| 66 | 
         
             
                const pathSlash = path ? `${path}/` : "";
         
     | 
| 67 | 
         
            -
             
     | 
| 68 | 
         
             
                const res = await fetch(`/api/dir/mkdir`, {
         
     | 
| 69 | 
         
             
                  method: 'POST',
         
     | 
| 70 | 
         
             
                  headers: { 'Content-Type': 'application/json' },
         
     | 
| 
         @@ -76,12 +76,12 @@ export default function () { 
     | 
|
| 76 | 
         
             
                  alert("Failed to create folder.");
         
     | 
| 77 | 
         
             
                }
         
     | 
| 78 | 
         
             
              }
         
     | 
| 79 | 
         
            -
             
     | 
| 80 | 
         
             
              async function deleteItem(item: any) {
         
     | 
| 81 | 
         
             
                if (!window.confirm(`Are you sure you want to delete "${item.name}"?`)) return;
         
     | 
| 82 | 
         
             
                const pathSlash = path ? `${path}/` : "";
         
     | 
| 83 | 
         | 
| 84 | 
         
            -
                const apiPath = item.type === "directory" ? `/api/dir/delete 
     | 
| 85 | 
         
             
                await fetch(apiPath, {
         
     | 
| 86 | 
         
             
                  method: "POST",
         
     | 
| 87 | 
         
             
                  headers: { "Content-Type": "application/json" },
         
     | 
| 
         @@ -109,39 +109,39 @@ export default function () { 
     | 
|
| 109 | 
         
             
                    {list.data && (
         
     | 
| 110 | 
         
             
                      <>
         
     | 
| 111 | 
         
             
                        <div className="actions">
         
     | 
| 112 | 
         
            -
             
     | 
| 113 | 
         
            -
             
     | 
| 114 | 
         
            -
             
     | 
| 115 | 
         
            -
             
     | 
| 116 | 
         
            -
             
     | 
| 117 | 
         
            -
             
     | 
| 118 | 
         
            -
             
     | 
| 119 | 
         
            -
             
     | 
| 120 | 
         
            -
             
     | 
| 121 | 
         
            -
             
     | 
| 122 | 
         
            -
             
     | 
| 123 | 
         
            -
             
     | 
| 124 | 
         
            -
             
     | 
| 125 | 
         
            -
             
     | 
| 126 | 
         
            -
             
     | 
| 127 | 
         
            -
             
     | 
| 128 | 
         
            -
             
     | 
| 129 | 
         
            -
             
     | 
| 130 | 
         
            -
             
     | 
| 131 | 
         
            -
             
     | 
| 132 | 
         
            -
             
     | 
| 133 | 
         
            -
             
     | 
| 134 | 
         
            -
             
     | 
| 135 | 
         
            -
             
     | 
| 136 | 
         
            -
             
     | 
| 137 | 
         
            -
             
     | 
| 138 | 
         
            -
             
     | 
| 139 | 
         
            -
             
     | 
| 140 | 
         
            -
             
     | 
| 141 | 
         
            -
             
     | 
| 142 | 
         
            -
             
     | 
| 143 | 
         
            -
             
     | 
| 144 | 
         
            -
             
     | 
| 145 | 
         
             
                        </div>
         
     | 
| 146 | 
         | 
| 147 | 
         
             
                        {path && (
         
     | 
| 
         @@ -155,7 +155,7 @@ export default function () { 
     | 
|
| 155 | 
         | 
| 156 | 
         
             
                        {list.data.map((item: any) => (
         
     | 
| 157 | 
         
             
                          <div key={item.name} className="entry">
         
     | 
| 158 | 
         
            -
                            <a key={link(item)}  
     | 
| 159 | 
         
             
                              {item.type === 'directory' ? <Folder /> : <File />}
         
     | 
| 160 | 
         
             
                              {shortName(item)}
         
     | 
| 161 | 
         
             
                            </a>
         
     | 
| 
         | 
|
| 29 | 
         
             
              const navigate = useNavigate();
         
     | 
| 30 | 
         
             
              const [isCreatingDir, setIsCreatingDir] = useState(false);
         
     | 
| 31 | 
         
             
              const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
         
     | 
| 32 | 
         
            +
             
     | 
| 33 | 
         
            +
             
     | 
| 34 | 
         
             
              function link(item: any) {
         
     | 
| 35 | 
         
             
                if (item.type === 'directory') {
         
     | 
| 36 | 
         
             
                  return `/dir/${item.name}`;
         
     | 
| 
         | 
|
| 38 | 
         
             
                  return `/edit/${item.name}`;
         
     | 
| 39 | 
         
             
                }
         
     | 
| 40 | 
         
             
              }
         
     | 
| 41 | 
         
            +
             
     | 
| 42 | 
         
             
              function shortName(item: any) {
         
     | 
| 43 | 
         
             
                return item.name.split('/').pop();
         
     | 
| 44 | 
         
             
              }
         
     | 
| 
         | 
|
| 53 | 
         
             
                  i++;
         
     | 
| 54 | 
         
             
                }
         
     | 
| 55 | 
         
             
              }
         
     | 
| 56 | 
         
            +
             
     | 
| 57 | 
         
             
              function newWorkspaceIn(path: string, list: any[], workspaceName?: string) {
         
     | 
| 58 | 
         
             
                const pathSlash = path ? `${path}/` : "";
         
     | 
| 59 | 
         
             
                const name = workspaceName || newName(list);
         
     | 
| 60 | 
         
            +
                navigate(`/edit/${pathSlash}${name}`, { replace: true });
         
     | 
| 61 | 
         
             
              }
         
     | 
| 62 | 
         
            +
             
     | 
| 63 | 
         | 
| 64 | 
         
             
              async function newFolderIn(path: string, list: any[], folderName?: string) {
         
     | 
| 65 | 
         
             
                const name = folderName || newName(list, "New Folder");
         
     | 
| 66 | 
         
             
                const pathSlash = path ? `${path}/` : "";
         
     | 
| 67 | 
         
            +
             
     | 
| 68 | 
         
             
                const res = await fetch(`/api/dir/mkdir`, {
         
     | 
| 69 | 
         
             
                  method: 'POST',
         
     | 
| 70 | 
         
             
                  headers: { 'Content-Type': 'application/json' },
         
     | 
| 
         | 
|
| 76 | 
         
             
                  alert("Failed to create folder.");
         
     | 
| 77 | 
         
             
                }
         
     | 
| 78 | 
         
             
              }
         
     | 
| 79 | 
         
            +
             
     | 
| 80 | 
         
             
              async function deleteItem(item: any) {
         
     | 
| 81 | 
         
             
                if (!window.confirm(`Are you sure you want to delete "${item.name}"?`)) return;
         
     | 
| 82 | 
         
             
                const pathSlash = path ? `${path}/` : "";
         
     | 
| 83 | 
         | 
| 84 | 
         
            +
                const apiPath = item.type === "directory" ? `/api/dir/delete` : `/api/delete`;
         
     | 
| 85 | 
         
             
                await fetch(apiPath, {
         
     | 
| 86 | 
         
             
                  method: "POST",
         
     | 
| 87 | 
         
             
                  headers: { "Content-Type": "application/json" },
         
     | 
| 
         | 
|
| 109 | 
         
             
                    {list.data && (
         
     | 
| 110 | 
         
             
                      <>
         
     | 
| 111 | 
         
             
                        <div className="actions">
         
     | 
| 112 | 
         
            +
                          <div className="new-workspace">
         
     | 
| 113 | 
         
            +
                            {isCreatingWorkspace &&
         
     | 
| 114 | 
         
            +
                              // @ts-ignore
         
     | 
| 115 | 
         
            +
                              <form onSubmit={(e) => { e.preventDefault(); newWorkspaceIn(path || "", list.data, e.target.workspaceName.value.trim()) }}>
         
     | 
| 116 | 
         
            +
                                <input
         
     | 
| 117 | 
         
            +
                                  type="text"
         
     | 
| 118 | 
         
            +
                                  name="workspaceName"
         
     | 
| 119 | 
         
            +
                                  defaultValue={newName(list.data)}
         
     | 
| 120 | 
         
            +
                                  placeholder={newName(list.data)}
         
     | 
| 121 | 
         
            +
                                />
         
     | 
| 122 | 
         
            +
                              </form>
         
     | 
| 123 | 
         
            +
                            }
         
     | 
| 124 | 
         
            +
                            <button type="button" onClick={() => setIsCreatingWorkspace(true)}>
         
     | 
| 125 | 
         
            +
                              <FolderPlus /> New workspace
         
     | 
| 126 | 
         
            +
                            </button>
         
     | 
| 127 | 
         
            +
                          </div>
         
     | 
| 128 | 
         
            +
             
     | 
| 129 | 
         
            +
                          <div className="new-folder">
         
     | 
| 130 | 
         
            +
                            {isCreatingDir &&
         
     | 
| 131 | 
         
            +
                              // @ts-ignore
         
     | 
| 132 | 
         
            +
                              <form onSubmit={(e) => { e.preventDefault(); newFolderIn(path || "", list.data, e.target.folderName.value.trim()) }}>
         
     | 
| 133 | 
         
            +
                                <input
         
     | 
| 134 | 
         
            +
                                  type="text"
         
     | 
| 135 | 
         
            +
                                  name="folderName"
         
     | 
| 136 | 
         
            +
                                  defaultValue={newName(list.data)}
         
     | 
| 137 | 
         
            +
                                  placeholder={newName(list.data)}
         
     | 
| 138 | 
         
            +
                                />
         
     | 
| 139 | 
         
            +
                              </form>
         
     | 
| 140 | 
         
            +
                            }
         
     | 
| 141 | 
         
            +
                            <button type="button" onClick={() => setIsCreatingDir(true)}>
         
     | 
| 142 | 
         
            +
                              <FolderPlus /> New folder
         
     | 
| 143 | 
         
            +
                            </button>
         
     | 
| 144 | 
         
            +
                          </div>
         
     | 
| 145 | 
         
             
                        </div>
         
     | 
| 146 | 
         | 
| 147 | 
         
             
                        {path && (
         
     | 
| 
         | 
|
| 155 | 
         | 
| 156 | 
         
             
                        {list.data.map((item: any) => (
         
     | 
| 157 | 
         
             
                          <div key={item.name} className="entry">
         
     | 
| 158 | 
         
            +
                            <a key={link(item)} href={link(item)}>
         
     | 
| 159 | 
         
             
                              {item.type === 'directory' ? <Folder /> : <File />}
         
     | 
| 160 | 
         
             
                              {shortName(item)}
         
     | 
| 161 | 
         
             
                            </a>
         
     | 
    	
        lynxkite-app/web/src/index.css
    CHANGED
    
    | 
         @@ -308,7 +308,6 @@ body { 
     | 
|
| 308 | 
         
             
              }
         
     | 
| 309 | 
         | 
| 310 | 
         
             
              a {
         
     | 
| 311 | 
         
            -
                color: black;
         
     | 
| 312 | 
         
             
                text-decoration: none;
         
     | 
| 313 | 
         
             
              }
         
     | 
| 314 | 
         | 
| 
         | 
|
| 308 | 
         
             
              }
         
     | 
| 309 | 
         | 
| 310 | 
         
             
              a {
         
     | 
| 
         | 
|
| 311 | 
         
             
                text-decoration: none;
         
     | 
| 312 | 
         
             
              }
         
     | 
| 313 | 
         | 
    	
        lynxkite-core/src/lynxkite/core/ops.py
    CHANGED
    
    | 
         @@ -206,8 +206,10 @@ def register_executor(env: str): 
     | 
|
| 206 | 
         | 
| 207 | 
         | 
| 208 | 
         
             
            def op_registration(env: str):
         
     | 
| 
         | 
|
| 209 | 
         
             
                return functools.partial(op, env)
         
     | 
| 210 | 
         | 
| 211 | 
         | 
| 212 | 
         
             
            def passive_op_registration(env: str):
         
     | 
| 
         | 
|
| 213 | 
         
             
                return functools.partial(register_passive_op, env)
         
     | 
| 
         | 
|
| 206 | 
         | 
| 207 | 
         | 
| 208 | 
         
             
            def op_registration(env: str):
         
     | 
| 209 | 
         
            +
                """Returns a decorator that can be used for registering functions as operations."""
         
     | 
| 210 | 
         
             
                return functools.partial(op, env)
         
     | 
| 211 | 
         | 
| 212 | 
         | 
| 213 | 
         
             
            def passive_op_registration(env: str):
         
     | 
| 214 | 
         
            +
                """Returns a function that can be used to register operations without associated code."""
         
     | 
| 215 | 
         
             
                return functools.partial(register_passive_op, env)
         
     | 
    	
        lynxkite-graph-analytics/pyproject.toml
    CHANGED
    
    | 
         @@ -17,8 +17,8 @@ dependencies = [ 
     | 
|
| 17 | 
         | 
| 18 | 
         
             
            [project.optional-dependencies]
         
     | 
| 19 | 
         
             
            dev = [
         
     | 
| 20 | 
         
            -
                "pytest",
         
     | 
| 21 | 
         
            -
                "pytest-asyncio",
         
     | 
| 22 | 
         
             
            ]
         
     | 
| 23 | 
         
             
            gpu = [
         
     | 
| 24 | 
         
             
                "nx-cugraph-cu12>=24.12.0",
         
     | 
| 
         @@ -28,4 +28,4 @@ gpu = [ 
     | 
|
| 28 | 
         
             
            lynxkite-core = { path = "../lynxkite-core" }
         
     | 
| 29 | 
         | 
| 30 | 
         
             
            [tool.pytest.ini_options]
         
     | 
| 31 | 
         
            -
            asyncio_mode = "auto"
         
     | 
| 
         | 
|
| 17 | 
         | 
| 18 | 
         
             
            [project.optional-dependencies]
         
     | 
| 19 | 
         
             
            dev = [
         
     | 
| 20 | 
         
            +
                "pytest>=8.3.4",
         
     | 
| 21 | 
         
            +
                "pytest-asyncio>=0.25.3",
         
     | 
| 22 | 
         
             
            ]
         
     | 
| 23 | 
         
             
            gpu = [
         
     | 
| 24 | 
         
             
                "nx-cugraph-cu12>=24.12.0",
         
     | 
| 
         | 
|
| 28 | 
         
             
            lynxkite-core = { path = "../lynxkite-core" }
         
     | 
| 29 | 
         | 
| 30 | 
         
             
            [tool.pytest.ini_options]
         
     | 
| 31 | 
         
            +
            asyncio_mode = "auto"
         
     | 
    	
        lynxkite-graph-analytics/src/lynxkite_graph_analytics/__init__.py
    ADDED
    
    | 
         @@ -0,0 +1,3 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            from . import lynxkite_ops  # noqa (imported to trigger registration)
         
     | 
| 2 | 
         
            +
            from . import networkx_ops  # noqa (imported to trigger registration)
         
     | 
| 3 | 
         
            +
            from . import pytorch_model_ops  # noqa (imported to trigger registration)
         
     | 
    	
        lynxkite-graph-analytics/src/{lynxkite_plugins/graph_analytics β lynxkite_graph_analytics}/lynxkite_ops.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        lynxkite-graph-analytics/src/{lynxkite_plugins/graph_analytics β lynxkite_graph_analytics}/networkx_ops.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        lynxkite-graph-analytics/src/{lynxkite_plugins/graph_analytics β lynxkite_graph_analytics}/pytorch_model_ops.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        lynxkite-graph-analytics/src/lynxkite_plugins/graph_analytics/__init__.py
    DELETED
    
    | 
         @@ -1,3 +0,0 @@ 
     | 
|
| 1 | 
         
            -
            from . import lynxkite_ops
         
     | 
| 2 | 
         
            -
            from . import networkx_ops
         
     | 
| 3 | 
         
            -
            from . import pytorch_model_ops
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
    	
        lynxkite-graph-analytics/tests/test_lynxkite_ops.py
    CHANGED
    
    | 
         @@ -3,7 +3,7 @@ import pytest 
     | 
|
| 3 | 
         
             
            import networkx as nx
         
     | 
| 4 | 
         | 
| 5 | 
         
             
            from lynxkite.core import workspace
         
     | 
| 6 | 
         
            -
            from  
     | 
| 7 | 
         | 
| 8 | 
         | 
| 9 | 
         
             
            async def test_execute_operation_not_in_catalog():
         
     | 
| 
         | 
|
| 3 | 
         
             
            import networkx as nx
         
     | 
| 4 | 
         | 
| 5 | 
         
             
            from lynxkite.core import workspace
         
     | 
| 6 | 
         
            +
            from lynxkite_graph_analytics.lynxkite_ops import Bundle, execute, op
         
     | 
| 7 | 
         | 
| 8 | 
         | 
| 9 | 
         
             
            async def test_execute_operation_not_in_catalog():
         
     | 
    	
        lynxkite-lynxscribe/README.md
    CHANGED
    
    | 
         @@ -5,7 +5,7 @@ LynxKite UI for building LynxScribe chat applications. Also runs the chat applic 
     | 
|
| 5 | 
         
             
            To run a chat UI for LynxScribe workspaces:
         
     | 
| 6 | 
         | 
| 7 | 
         
             
            ```bash
         
     | 
| 8 | 
         
            -
            WEBUI_AUTH=false OPENAI_API_BASE_URL=http://localhost:8000/api/service/ 
     | 
| 9 | 
         
             
            ```
         
     | 
| 10 | 
         | 
| 11 | 
         
             
            Or use [Lynx WebUI](https://github.com/biggraph/lynx-webui/) instead of Open WebUI.
         
     | 
| 
         | 
|
| 5 | 
         
             
            To run a chat UI for LynxScribe workspaces:
         
     | 
| 6 | 
         | 
| 7 | 
         
             
            ```bash
         
     | 
| 8 | 
         
            +
            WEBUI_AUTH=false OPENAI_API_BASE_URL=http://localhost:8000/api/service/lynxscribe/lynxscribe_ops uvx open-webui serve
         
     | 
| 9 | 
         
             
            ```
         
     | 
| 10 | 
         | 
| 11 | 
         
             
            Or use [Lynx WebUI](https://github.com/biggraph/lynx-webui/) instead of Open WebUI.
         
     | 
    	
        lynxkite-lynxscribe/src/{lynxkite_plugins/lynxscribe β lynxkite_lynxscribe}/__init__.py
    RENAMED
    
    | 
         @@ -1,5 +1,5 @@ 
     | 
|
| 1 | 
         
            -
            from . import lynxscribe_ops
         
     | 
| 2 | 
         
            -
            from . import llm_ops
         
     | 
| 3 | 
         
             
            from .lynxscribe_ops import api_service_post, api_service_get
         
     | 
| 4 | 
         | 
| 5 | 
         
             
            __all__ = ["api_service_post", "api_service_get"]
         
     | 
| 
         | 
|
| 1 | 
         
            +
            from . import lynxscribe_ops  # noqa (imported to trigger registration)
         
     | 
| 2 | 
         
            +
            from . import llm_ops  # noqa (imported to trigger registration)
         
     | 
| 3 | 
         
             
            from .lynxscribe_ops import api_service_post, api_service_get
         
     | 
| 4 | 
         | 
| 5 | 
         
             
            __all__ = ["api_service_post", "api_service_get"]
         
     | 
    	
        lynxkite-lynxscribe/src/{lynxkite_plugins/lynxscribe β lynxkite_lynxscribe}/llm_ops.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        lynxkite-lynxscribe/src/{lynxkite_plugins/lynxscribe β lynxkite_lynxscribe}/lynxscribe_ops.py
    RENAMED
    
    | 
         
            File without changes
         
     | 
    	
        lynxkite-pillow-example/pyproject.toml
    CHANGED
    
    | 
         @@ -8,8 +8,8 @@ dependencies = [ 
     | 
|
| 8 | 
         
             
                "fsspec>=2025.2.0",
         
     | 
| 9 | 
         
             
                "lynxkite-core",
         
     | 
| 10 | 
         
             
                "pillow>=11.1.0",
         
     | 
| 11 | 
         
            -
                "requests",
         
     | 
| 12 | 
         
            -
                "aiohttp",
         
     | 
| 13 | 
         
             
            ]
         
     | 
| 14 | 
         | 
| 15 | 
         
             
            [tool.uv.sources]
         
     | 
| 
         | 
|
| 8 | 
         
             
                "fsspec>=2025.2.0",
         
     | 
| 9 | 
         
             
                "lynxkite-core",
         
     | 
| 10 | 
         
             
                "pillow>=11.1.0",
         
     | 
| 11 | 
         
            +
                "requests>=2.32.3",
         
     | 
| 12 | 
         
            +
                "aiohttp>=3.11.11",
         
     | 
| 13 | 
         
             
            ]
         
     | 
| 14 | 
         | 
| 15 | 
         
             
            [tool.uv.sources]
         
     | 
    	
        lynxkite-pillow-example/src/{lynxkite_plugins/pillow_example β lynxkite_pillow_example}/__init__.py
    RENAMED
    
    | 
         
            File without changes
         
     |