model-compose 0.0.12__tar.gz → 0.0.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. {model_compose-0.0.12/src/model_compose.egg-info → model_compose-0.0.13}/PKG-INFO +2 -1
  2. {model_compose-0.0.12 → model_compose-0.0.13}/pyproject.toml +11 -2
  3. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/component/component.py +3 -3
  4. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/component/engine/base.py +4 -3
  5. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/component/engine/http_client.py +4 -6
  6. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/component/engine/http_server.py +2 -2
  7. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/compose/manager.py +2 -2
  8. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/controller/controller.py +2 -2
  9. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/controller/engine/base.py +31 -7
  10. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/controller/engine/http_server.py +11 -3
  11. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/controller/engine/mcp_server.py +11 -3
  12. model_compose-0.0.13/src/mindor/core/controller/runner/__init__.py +1 -0
  13. model_compose-0.0.13/src/mindor/core/controller/runner/client.py +11 -0
  14. model_compose-0.0.13/src/mindor/core/controller/runner/http_client.py +26 -0
  15. model_compose-0.0.13/src/mindor/core/controller/runner/mcp_client.py +10 -0
  16. model_compose-0.0.13/src/mindor/core/controller/runner/runner.py +33 -0
  17. model_compose-0.0.13/src/mindor/core/controller/webui/__init__.py +1 -0
  18. model_compose-0.0.13/src/mindor/core/controller/webui/gradio.py +88 -0
  19. model_compose-0.0.13/src/mindor/core/controller/webui/schema.py +94 -0
  20. model_compose-0.0.13/src/mindor/core/controller/webui/webui.py +74 -0
  21. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/listener/engine/base.py +2 -2
  22. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/listener/engine/http_callback.py +2 -2
  23. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/listener/listener.py +2 -2
  24. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/utils/template.py +5 -5
  25. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/workflow/job.py +4 -4
  26. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/workflow/workflow.py +5 -12
  27. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/action/impl/http_client.py +0 -7
  28. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/impl/common.py +2 -0
  29. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/impl/http_client.py +7 -6
  30. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/controller/impl/common.py +2 -0
  31. model_compose-0.0.13/src/mindor/dsl/schema/controller/impl/webui.py +7 -0
  32. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/workflow.py +10 -1
  33. {model_compose-0.0.12 → model_compose-0.0.13/src/model_compose.egg-info}/PKG-INFO +2 -1
  34. {model_compose-0.0.12 → model_compose-0.0.13}/src/model_compose.egg-info/SOURCES.txt +10 -0
  35. {model_compose-0.0.12 → model_compose-0.0.13}/src/model_compose.egg-info/requires.txt +1 -0
  36. {model_compose-0.0.12 → model_compose-0.0.13}/LICENSE +0 -0
  37. {model_compose-0.0.12 → model_compose-0.0.13}/README.md +0 -0
  38. {model_compose-0.0.12 → model_compose-0.0.13}/setup.cfg +0 -0
  39. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/__init__.py +0 -0
  40. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/cli/__init__.py +0 -0
  41. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/cli/compose.py +0 -0
  42. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/__init__.py +0 -0
  43. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/component/__init__.py +0 -0
  44. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/component/engine/__init__.py +0 -0
  45. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/component/engine/context.py +0 -0
  46. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/compose/__init__.py +0 -0
  47. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/compose/compose.py +0 -0
  48. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/controller/__init__.py +0 -0
  49. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/controller/engine/__init__.py +0 -0
  50. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/gateway/__init__.py +0 -0
  51. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/gateway/engine/__init__.py +0 -0
  52. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/gateway/engine/base.py +0 -0
  53. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
  54. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/gateway/gateway.py +0 -0
  55. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/listener/__init__.py +0 -0
  56. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/listener/engine/__init__.py +0 -0
  57. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/runtime/__init__.py +0 -0
  58. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/runtime/env.py +0 -0
  59. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/services/__init__.py +0 -0
  60. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/services/async_service.py +0 -0
  61. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/utils/__init__.py +0 -0
  62. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/utils/expiring.py +0 -0
  63. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/utils/http_client.py +0 -0
  64. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/utils/http_headers.py +0 -0
  65. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/utils/resources.py +0 -0
  66. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/utils/time.py +0 -0
  67. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/utils/workqueue.py +0 -0
  68. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/workflow/__init__.py +0 -0
  69. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/core/workflow/context.py +0 -0
  70. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/__init__.py +0 -0
  71. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/loader.py +0 -0
  72. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/__init__.py +0 -0
  73. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/action/__init__.py +0 -0
  74. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/action/action.py +0 -0
  75. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
  76. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/action/impl/common.py +0 -0
  77. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
  78. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -0
  79. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
  80. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/__init__.py +0 -0
  81. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/component.py +0 -0
  82. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
  83. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/impl/http_server.py +0 -0
  84. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/impl/mcp_client.py +0 -0
  85. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/impl/mcp_server.py +0 -0
  86. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/component/impl/types.py +0 -0
  87. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/compose.py +0 -0
  88. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/controller/__init__.py +0 -0
  89. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/controller/controller.py +0 -0
  90. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
  91. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
  92. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/controller/impl/mcp_server.py +0 -0
  93. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
  94. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
  95. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/gateway/gateway.py +0 -0
  96. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/gateway/impl/__init__.py +0 -0
  97. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
  98. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
  99. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/gateway/impl/types.py +0 -0
  100. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/listener/__init__.py +0 -0
  101. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
  102. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
  103. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/listener/impl/http_callback.py +0 -0
  104. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
  105. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/listener/listener.py +0 -0
  106. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
  107. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
  108. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
  109. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
  110. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
  111. {model_compose-0.0.12 → model_compose-0.0.13}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
  112. {model_compose-0.0.12 → model_compose-0.0.13}/src/model_compose.egg-info/dependency_links.txt +0 -0
  113. {model_compose-0.0.12 → model_compose-0.0.13}/src/model_compose.egg-info/entry_points.txt +0 -0
  114. {model_compose-0.0.12 → model_compose-0.0.13}/src/model_compose.egg-info/top_level.txt +0 -0
  115. {model_compose-0.0.12 → model_compose-0.0.13}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model-compose
3
- Version: 0.0.12
3
+ Version: 0.0.13
4
4
  Summary: model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)
5
5
  Author-email: Hanyeol Cho <hanyeol.cho@gmail.com>
6
6
  License-Expression: MIT
@@ -16,6 +16,7 @@ Requires-Dist: requests
16
16
  Requires-Dist: fastapi
17
17
  Requires-Dist: uvicorn
18
18
  Requires-Dist: ulid
19
+ Requires-Dist: gradio
19
20
  Dynamic: license-file
20
21
 
21
22
  # model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "model-compose"
3
- version = "0.0.12"
3
+ version = "0.0.13"
4
4
  description = "model-compose: Declarative AI Model and Workflow Orchestrator (from Mindor)"
5
5
  authors = [
6
6
  { name = "Hanyeol Cho", email = "hanyeol.cho@gmail.com" }
@@ -8,7 +8,16 @@ authors = [
8
8
  readme = "README.md"
9
9
  license = "MIT"
10
10
  dependencies = [
11
- "click", "pyyaml", "pydantic", "python-dotenv", "aiohttp", "requests", "fastapi", "uvicorn", "ulid"
11
+ "click",
12
+ "pyyaml",
13
+ "pydantic",
14
+ "python-dotenv",
15
+ "aiohttp",
16
+ "requests",
17
+ "fastapi",
18
+ "uvicorn",
19
+ "ulid",
20
+ "gradio"
12
21
  ]
13
22
 
14
23
  [project.scripts]
@@ -1,8 +1,8 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
2
  from mindor.dsl.schema.component import ComponentConfig
3
- from .engine import BaseComponent, ComponentEngineMap
3
+ from .engine import ComponentEngine, ComponentEngineMap
4
4
 
5
- ComponentInstances: Dict[str, BaseComponent] = {}
5
+ ComponentInstances: Dict[str, ComponentEngine] = {}
6
6
 
7
7
  class ComponentResolver:
8
8
  def __init__(self, components: Dict[str, ComponentConfig]):
@@ -27,7 +27,7 @@ class ComponentResolver:
27
27
 
28
28
  return default_ids[0] if default_ids else "__default__"
29
29
 
30
- def create_component(id: str, config: ComponentConfig, env: Dict[str, str], daemon: bool) -> BaseComponent:
30
+ def create_component(id: str, config: ComponentConfig, env: Dict[str, str], daemon: bool) -> ComponentEngine:
31
31
  try:
32
32
  component = ComponentInstances[id] if id in ComponentInstances else None
33
33
 
@@ -30,14 +30,15 @@ class ActionResolver:
30
30
 
31
31
  return default_ids[0] if default_ids else "__default__"
32
32
 
33
- class BaseComponent(AsyncService):
33
+ class ComponentEngine(AsyncService):
34
34
  def __init__(self, id: str, config: ComponentConfig, env: Dict[str, str], daemon: bool):
35
35
  super().__init__(daemon)
36
36
 
37
37
  self.id: str = id
38
38
  self.config: ComponentConfig = config
39
39
  self.env: Dict[str, str] = env
40
-
40
+ self.queue: Optional[WorkQueue] = None
41
+
41
42
  if self.config.max_concurrent_count > 0:
42
43
  self.queue = WorkQueue(self.config.max_concurrent_count, self._run)
43
44
 
@@ -66,4 +67,4 @@ class BaseComponent(AsyncService):
66
67
  async def _run(self, action: ActionConfig, context: ComponentContext) -> Any:
67
68
  pass
68
69
 
69
- ComponentEngineMap: Dict[ComponentType, Type[BaseComponent]] = {}
70
+ ComponentEngineMap: Dict[ComponentType, Type[ComponentEngine]] = {}
@@ -4,7 +4,7 @@ from mindor.dsl.schema.action import ActionConfig, HttpClientActionConfig, HttpC
4
4
  from mindor.core.listener import HttpCallbackListener
5
5
  from mindor.core.utils.http_client import HttpClient
6
6
  from mindor.core.utils.time import parse_duration
7
- from .base import BaseComponent, ComponentType, ComponentEngineMap
7
+ from .base import ComponentEngine, ComponentType, ComponentEngineMap
8
8
  from .context import ComponentContext
9
9
  from datetime import datetime, timezone
10
10
  import asyncio
@@ -79,7 +79,7 @@ class HttpClientAction:
79
79
  body = context.render_template(self.config.body)
80
80
  headers = context.render_template(self.config.headers)
81
81
 
82
- response = await self.client.request(url, method, params, body, headers)
82
+ response, result = await self.client.request(url, method, params, body, headers), None
83
83
 
84
84
  if response:
85
85
  context.register_source("response", response)
@@ -90,9 +90,7 @@ class HttpClientAction:
90
90
  if result:
91
91
  context.register_source("result", result)
92
92
 
93
- return context.render_template(self.config.output) if self.config.output else result
94
-
95
- return context.render_template(self.config.output) if self.config.output else response
93
+ return context.render_template(self.config.output) if self.config.output else (result or response)
96
94
 
97
95
  def _resolve_request_url(self, context: ComponentContext) -> str:
98
96
  if self.base_url and self.config.path:
@@ -109,7 +107,7 @@ class HttpClientAction:
109
107
 
110
108
  return None
111
109
 
112
- class HttpClientComponent(BaseComponent):
110
+ class HttpClientComponent(ComponentEngine):
113
111
  def __init__(self, id: str, config: HttpClientComponentConfig, env: Dict[str, str], daemon: bool):
114
112
  super().__init__(id, config, env, daemon)
115
113
 
@@ -1,9 +1,9 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
2
  from mindor.dsl.schema.component import HttpServerComponentConfig
3
- from .base import BaseComponent, ComponentType, ComponentEngineMap, ActionConfig
3
+ from .base import ComponentEngine, ComponentType, ComponentEngineMap, ActionConfig
4
4
  from .context import ComponentContext
5
5
 
6
- class HttpServerComponent(BaseComponent):
6
+ class HttpServerComponent(ComponentEngine):
7
7
  def __init__(self, id: str, config: HttpServerComponentConfig, env: Dict[str, str], daemon: bool):
8
8
  super().__init__(id, config, env, daemon)
9
9
 
@@ -1,11 +1,11 @@
1
1
  from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
2
  from mindor.dsl.schema.compose import ComposeConfig
3
- from mindor.core.controller import BaseController, TaskState, create_controller
3
+ from mindor.core.controller import ControllerEngine, TaskState, create_controller
4
4
 
5
5
  class ComposeManager:
6
6
  def __init__(self, config: ComposeConfig, env: Dict[str, str], daemon: bool = True):
7
7
  self.config: ComposeConfig = config
8
- self.controller: BaseController = create_controller(self.config.controller, self.config.components, self.config.listeners, self.config.workflows, env, daemon)
8
+ self.controller: ControllerEngine = create_controller(self.config.controller, self.config.components, self.config.listeners, self.config.workflows, env, daemon)
9
9
 
10
10
  async def launch_services(self, detach: bool, background: bool = False):
11
11
  await self.controller.start(background=background)
@@ -3,9 +3,9 @@ from mindor.dsl.schema.controller import ControllerConfig
3
3
  from mindor.dsl.schema.component import ComponentConfig
4
4
  from mindor.dsl.schema.listener import ListenerConfig
5
5
  from mindor.dsl.schema.workflow import WorkflowConfig
6
- from .engine import BaseController, ControllerEngineMap
6
+ from .engine import ControllerEngine, ControllerEngineMap
7
7
 
8
- def create_controller(config: ControllerConfig, components: Dict[str, ComponentConfig], listeners: List[ListenerConfig], workflows: Dict[str, WorkflowConfig], env: Dict[str, str], daemon: bool) -> BaseController:
8
+ def create_controller(config: ControllerConfig, components: Dict[str, ComponentConfig], listeners: List[ListenerConfig], workflows: Dict[str, WorkflowConfig], env: Dict[str, str], daemon: bool) -> ControllerEngine:
9
9
  try:
10
10
  return ControllerEngineMap[config.type](config, components, listeners, workflows, env, daemon)
11
11
  except KeyError:
@@ -6,9 +6,10 @@ from mindor.dsl.schema.component import ComponentConfig
6
6
  from mindor.dsl.schema.listener import ListenerConfig
7
7
  from mindor.dsl.schema.workflow import WorkflowConfig
8
8
  from mindor.core.services import AsyncService
9
- from mindor.core.component import BaseComponent, create_component
10
- from mindor.core.listener import BaseListener, create_listener
9
+ from mindor.core.component import ComponentEngine, create_component
10
+ from mindor.core.listener import ListenerEngine, create_listener
11
11
  from mindor.core.workflow import Workflow, WorkflowResolver, create_workflow
12
+ from mindor.core.controller.webui import ControllerWebUI
12
13
  from mindor.core.utils.workqueue import WorkQueue
13
14
  from mindor.core.utils.expiring import ExpiringDict
14
15
 
@@ -22,8 +23,16 @@ class TaskState:
22
23
  output: Optional[Any] = None
23
24
  error: Optional[Any] = None
24
25
 
25
- class BaseController(AsyncService):
26
- def __init__(self, config: ControllerConfig, components: Dict[str, ComponentConfig], listeners: List[ListenerConfig], workflows: Dict[str, WorkflowConfig], env: Dict[str, str], daemon: bool):
26
+ class ControllerEngine(AsyncService):
27
+ def __init__(
28
+ self,
29
+ config: ControllerConfig,
30
+ components: Dict[str, ComponentConfig],
31
+ listeners: List[ListenerConfig],
32
+ workflows: Dict[str, WorkflowConfig],
33
+ env: Dict[str, str],
34
+ daemon: bool
35
+ ):
27
36
  super().__init__(daemon)
28
37
 
29
38
  self.config: ControllerConfig = config
@@ -66,6 +75,9 @@ class BaseController(AsyncService):
66
75
  await self._start_components()
67
76
  await self._start_listeners()
68
77
 
78
+ if self.config.webui:
79
+ await self._start_webui()
80
+
69
81
  await super()._start()
70
82
 
71
83
  async def _stop(self) -> None:
@@ -76,6 +88,9 @@ class BaseController(AsyncService):
76
88
  await self._stop_listeners()
77
89
  await self._stop_components()
78
90
 
91
+ if self.config.webui:
92
+ await self._stop_webui()
93
+
79
94
  await super()._stop()
80
95
 
81
96
  async def _start_components(self) -> None:
@@ -90,6 +105,12 @@ class BaseController(AsyncService):
90
105
  async def _stop_listeners(self) -> None:
91
106
  await asyncio.gather(*[ listener.stop() for listener in self._create_listeners() ])
92
107
 
108
+ async def _start_webui(self) -> None:
109
+ await asyncio.gather(*[ self._create_webui().start() ])
110
+
111
+ async def _stop_webui(self) -> None:
112
+ await asyncio.gather(*[ self._create_webui().stop() ])
113
+
93
114
  async def _run_workflow(self, task_id: str, workflow_id: Optional[str], input: Dict[str, Any]) -> TaskState:
94
115
  state = TaskState(task_id=task_id, status="processing")
95
116
  with self.task_states_lock:
@@ -107,13 +128,16 @@ class BaseController(AsyncService):
107
128
 
108
129
  return state
109
130
 
110
- def _create_components(self) -> List[BaseComponent]:
131
+ def _create_components(self) -> List[ComponentEngine]:
111
132
  return [ create_component(component_id, config, self.env, self.daemon) for component_id, config in self.components.items() ]
112
133
 
113
- def _create_listeners(self) -> List[BaseListener]:
134
+ def _create_listeners(self) -> List[ListenerEngine]:
114
135
  return [ create_listener(config, self.env, self.daemon) for config in self.listeners ]
115
136
 
137
+ def _create_webui(self) -> ControllerWebUI:
138
+ return ControllerWebUI(self.config.webui, self.config, self.components, self.workflows, self.env, self.daemon)
139
+
116
140
  def _create_workflow(self, workflow_id: Optional[str]) -> Workflow:
117
141
  return create_workflow(*WorkflowResolver(self.workflows).resolve(workflow_id), self.components, self.env)
118
142
 
119
- ControllerEngineMap: Dict[ControllerType, Type[BaseController]] = {}
143
+ ControllerEngineMap: Dict[ControllerType, Type[ControllerEngine]] = {}
@@ -7,7 +7,7 @@ from mindor.dsl.schema.listener import ListenerConfig
7
7
  from mindor.dsl.schema.component import ComponentConfig
8
8
  from mindor.dsl.schema.workflow import WorkflowConfig
9
9
  from mindor.core.utils.resources import StreamResource
10
- from .base import BaseController, ControllerType, ControllerEngineMap, TaskState
10
+ from .base import ControllerEngine, ControllerType, ControllerEngineMap, TaskState
11
11
 
12
12
  from fastapi import FastAPI, APIRouter, Body, HTTPException
13
13
  from fastapi.responses import Response, JSONResponse, StreamingResponse
@@ -35,8 +35,16 @@ class TaskResult(BaseModel):
35
35
  error=instance.error
36
36
  )
37
37
 
38
- class HttpServerController(BaseController):
39
- def __init__(self, config: HttpServerControllerConfig, components: Dict[str, ComponentConfig], listeners: List[ListenerConfig], workflows: Dict[str, WorkflowConfig], env: Dict[str, str], daemon: bool):
38
+ class HttpServerController(ControllerEngine):
39
+ def __init__(
40
+ self,
41
+ config: HttpServerControllerConfig,
42
+ components: Dict[str, ComponentConfig],
43
+ listeners: List[ListenerConfig],
44
+ workflows: Dict[str, WorkflowConfig],
45
+ env: Dict[str, str],
46
+ daemon: bool
47
+ ):
40
48
  super().__init__(config, components, listeners, workflows, env, daemon)
41
49
 
42
50
  self.server: Optional[uvicorn.Server] = None
@@ -3,10 +3,18 @@ from mindor.dsl.schema.controller import McpServerControllerConfig
3
3
  from mindor.dsl.schema.listener import ListenerConfig
4
4
  from mindor.dsl.schema.component import ComponentConfig
5
5
  from mindor.dsl.schema.workflow import WorkflowConfig
6
- from .base import BaseController, ControllerType, ControllerEngineMap
6
+ from .base import ControllerEngine, ControllerType, ControllerEngineMap
7
7
 
8
- class McpServerController(BaseController):
9
- def __init__(self, config: McpServerControllerConfig, components: Dict[str, ComponentConfig], listeners: List[ListenerConfig], workflows: Dict[str, WorkflowConfig], env: Dict[str, str], daemon: bool):
8
+ class McpServerController(ControllerEngine):
9
+ def __init__(
10
+ self,
11
+ config: McpServerControllerConfig,
12
+ components: Dict[str, ComponentConfig],
13
+ listeners: List[ListenerConfig],
14
+ workflows: Dict[str, WorkflowConfig],
15
+ env: Dict[str, str],
16
+ daemon: bool
17
+ ):
10
18
  super().__init__(config, components, listeners, workflows, env, daemon)
11
19
 
12
20
  async def _serve(self) -> None:
@@ -0,0 +1 @@
1
+ from .runner import *
@@ -0,0 +1,11 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from abc import ABC, abstractmethod
3
+ from mindor.dsl.schema.controller import ControllerConfig
4
+
5
+ class ControllerClient(ABC):
6
+ def __init__(self, config: ControllerConfig):
7
+ self.config: ControllerConfig = config
8
+
9
+ @abstractmethod
10
+ async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
11
+ pass
@@ -0,0 +1,26 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.controller import ControllerConfig
3
+ from mindor.core.utils.http_client import HttpClient
4
+ from .runner import ControllerClient
5
+
6
+ class HttpControllerClient(ControllerClient):
7
+ def __init__(self, config: ControllerConfig):
8
+ super().__init__(config)
9
+
10
+ self.client: HttpClient = HttpClient()
11
+
12
+ async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
13
+ base_path = self.config.base_path if self.config.base_path else ""
14
+ url = f"http://localhost:{self.config.port}{base_path}/workflows"
15
+ method = "POST"
16
+ body = {
17
+ "workflow_id": workflow_id,
18
+ "input": input,
19
+ "wait_for_completion": True,
20
+ "output_only": True
21
+ }
22
+ headers = {
23
+ "Content-Type": "application/json"
24
+ }
25
+
26
+ return await self.client.request(url, method, None, body, headers)
@@ -0,0 +1,10 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.controller import ControllerConfig
3
+ from .runner import ControllerClient
4
+
5
+ class McpControllerClient(ControllerClient):
6
+ def __init__(self, config: ControllerConfig):
7
+ super().__init__(config)
8
+
9
+ async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
10
+ pass
@@ -0,0 +1,33 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from abc import ABC, abstractmethod
3
+ from mindor.dsl.schema.controller import ControllerConfig
4
+ from .client import ControllerClient
5
+ from .http_client import HttpControllerClient
6
+ from .mcp_client import McpControllerClient
7
+
8
+ class ControllerClient(ABC):
9
+ def __init__(self, config: ControllerConfig):
10
+ self.config: ControllerConfig = config
11
+
12
+ @abstractmethod
13
+ async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
14
+ pass
15
+
16
+ class ControllerRunner:
17
+ def __init__(self, config: ControllerConfig):
18
+ self.config: ControllerConfig = config
19
+ self.client: ControllerClient = None
20
+
21
+ self._configure_client()
22
+
23
+ def _configure_client(self) -> None:
24
+ if self.config.type == "http-server":
25
+ self.client = HttpControllerClient(self.config)
26
+ return
27
+
28
+ if self.config.type == "mcp-server":
29
+ self.client = McpControllerClient(self.config)
30
+ return
31
+
32
+ async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
33
+ return await self.client.run_workflow(workflow_id, input)
@@ -0,0 +1 @@
1
+ from .webui import *
@@ -0,0 +1,88 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Callable, Awaitable, Any
2
+ from mindor.dsl.schema.workflow import WorkflowVariableConfig
3
+ from .schema import WorkflowSchema
4
+ import gradio, json
5
+
6
+ class GradioWebUIBuilder:
7
+ def build(self, schema: Dict[str, WorkflowSchema], runner: Callable[[Optional[str], Any], Awaitable[Any]]) -> gradio.Blocks:
8
+ with gradio.Blocks() as blocks:
9
+ for workflow_id, workflow in schema.items():
10
+ async def run_workflow(input: Any, workflow_id=workflow_id) -> Any:
11
+ return await runner(workflow_id, input)
12
+
13
+ if len(schema) > 1:
14
+ with gradio.Tab(label=workflow.name or workflow_id):
15
+ self._build_workflow_section(workflow, run_workflow)
16
+ else:
17
+ self._build_workflow_section(workflow, run_workflow)
18
+
19
+ return blocks
20
+
21
+ def _build_workflow_section(self, workflow: WorkflowSchema, runner: Callable[[Any], Awaitable[Any]]) -> gradio.Column:
22
+ with gradio.Column() as section:
23
+ gradio.Markdown(f"## **{workflow.title or 'Untitled Workflow'}**")
24
+
25
+ if workflow.description:
26
+ gradio.Markdown(f"📝 {workflow.description}")
27
+
28
+ gradio.Markdown("#### 📥 Input Parameters")
29
+ input_components = [ self._build_input_component(variable) for variable in workflow.input ]
30
+ run_button = gradio.Button("🚀 Run Workflow", variant="primary")
31
+
32
+ gradio.Markdown("#### 📤 Output Values")
33
+ output_components = [ self._build_output_component(variable) for variable in workflow.output ]
34
+
35
+ if not output_components:
36
+ output_components = gradio.Textbox(label="", lines=8, interactive=False, show_copy_button=True)
37
+
38
+ async def run_workflow(*args):
39
+ input = { variable.name: value for variable, value in zip(workflow.input, args) }
40
+ output = await runner(input)
41
+
42
+ if workflow.output and isinstance(output, dict):
43
+ output = [ output[variable.name] for variable in workflow.output ]
44
+ output = output[0] if len(output) == 1 else output
45
+
46
+ return output
47
+
48
+ run_button.click(
49
+ fn=run_workflow,
50
+ inputs=input_components,
51
+ outputs=output_components
52
+ )
53
+
54
+ return section
55
+
56
+ def _build_input_component(self, variable: WorkflowVariableConfig) -> gradio.Component:
57
+ label = f"{variable.name} {'*' if variable.required else ''}"
58
+ info = variable.description or ""
59
+ default = variable.default
60
+
61
+ if variable.type == "string":
62
+ return gradio.Textbox(label=label, value=default or "", info=info)
63
+
64
+ if variable.type == "integer":
65
+ return gradio.Number(label=label, value=default or 0, precision=0, info=info)
66
+
67
+ if variable.type == "float":
68
+ return gradio.Number(label=label, value=default or 0.0, info=info)
69
+
70
+ if variable.type == "boolean":
71
+ return gradio.Checkbox(label=label, value=default or False, info=info)
72
+
73
+ if variable.type == "file":
74
+ return gradio.File(label=label, file_types=["*"], info=info)
75
+
76
+ if variable.type == "select":
77
+ return gradio.Dropdown(choices=variable.options or [], label=label, value=default, info=info)
78
+
79
+ return gradio.Textbox(label=label, value=default or "", info=f"Unsupported type: {variable.type}")
80
+
81
+ def _build_output_component(self, variable: WorkflowVariableConfig) -> gradio.Component:
82
+ label = variable.name
83
+ info = variable.description or ""
84
+
85
+ if variable.type == "string":
86
+ return gradio.Textbox(label=label, interactive=False, show_copy_button=True, info=info)
87
+
88
+ return gradio.Textbox(label=label, info=f"Unsupported type: {variable.type}")
@@ -0,0 +1,94 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from pydantic import BaseModel
3
+ from mindor.dsl.schema.workflow import WorkflowConfig, WorkflowVariableConfig
4
+ from mindor.dsl.schema.component import ComponentConfig
5
+ import re
6
+
7
+ class WorkflowVariableResolver:
8
+ def __init__(self):
9
+ self.patterns: Dict[str, re.Pattern] = {
10
+ "variable": re.compile(
11
+ r"""\$\{ # ${
12
+ ([a-zA-Z_][\w\-]*) # key: input, env, etc.
13
+ (?:\.([^\|\}]+))? # path: key, key.path[0], etc.
14
+ (?:\s*\|\s*([^\}]+))? # default value after `|`
15
+ \}""", # }
16
+ re.VERBOSE,
17
+ )
18
+ }
19
+
20
+ def _enumerate_variables(self, data: Any, wanted_key: str) -> Any:
21
+ if isinstance(data, str):
22
+ variables = []
23
+
24
+ for match in self.patterns["variable"].finditer(data):
25
+ key, path = match.group(1), match.group(2)
26
+
27
+ if key == wanted_key:
28
+ variables.append(path)
29
+
30
+ return variables
31
+
32
+ if isinstance(data, BaseModel):
33
+ return self._enumerate_variables(data.model_dump(exclude_none=True), wanted_key)
34
+
35
+ if isinstance(data, dict):
36
+ return sum([ self._enumerate_variables(v, wanted_key) for v in data.values() ], [])
37
+
38
+ if isinstance(data, list):
39
+ return sum([ self._enumerate_variables(v, wanted_key) for v in data ], [])
40
+
41
+ return []
42
+
43
+ class WorkflowInputResolver(WorkflowVariableResolver):
44
+ def resolve(self, workflow: WorkflowConfig, components: Dict[str, ComponentConfig]) -> List[WorkflowVariableConfig]:
45
+ variables = []
46
+ for job in workflow.jobs.values():
47
+ if not job.input or job.input == "${input}":
48
+ action_id = job.action or "__default__"
49
+ if isinstance(job.component, str):
50
+ component: ComponentConfig = components[job.component] if job.component in components else None
51
+ if component:
52
+ variables.extend(self._enumerate_variables(component.actions[action_id], "input"))
53
+ else:
54
+ variables.extend(self._enumerate_variables(job.component.actions[action_id], "input"))
55
+ else:
56
+ variables.extend(self._enumerate_variables(job.input, "input"))
57
+
58
+ return [ WorkflowVariableConfig(name=name, type="string") for name in set(variables) ]
59
+
60
+ class WorkflowOutputResolver(WorkflowVariableResolver):
61
+ def resolve(self, workflow: WorkflowConfig, components: Dict[str, ComponentConfig]) -> List[WorkflowVariableConfig]:
62
+ variables = []
63
+ for job_id, job in workflow.jobs.items():
64
+ if not self._is_terminal_job(workflow, job_id):
65
+ continue
66
+
67
+ if not job.output or job.output == "${output}":
68
+ action_id = job.action or "__default__"
69
+ if isinstance(job.component, str):
70
+ component: ComponentConfig = components[job.component] if job.component in components else None
71
+ if component:
72
+ action = component.actions[action_id]
73
+ if isinstance(action.output, dict):
74
+ variables.extend(action.output.keys())
75
+ else:
76
+ action = component.actions[action_id]
77
+ if isinstance(action.output, dict):
78
+ variables.extend(action.output.keys())
79
+ else:
80
+ if isinstance(job.output, dict):
81
+ variables.extend(job.output.keys())
82
+
83
+ return [ WorkflowVariableConfig(name=name, type="string") for name in set(variables) ]
84
+
85
+ def _is_terminal_job(self, workflow: WorkflowConfig, job_id: str) -> bool:
86
+ return all(job_id not in job.depends_on for other_id, job in workflow.jobs.items() if other_id != job_id)
87
+
88
+ class WorkflowSchema:
89
+ def __init__(self, name: str, title: str, description: Optional[str], input: List[WorkflowVariableConfig], output: List[WorkflowVariableConfig]):
90
+ self.name: str = name
91
+ self.title: str = title
92
+ self.description: Optional[str] = description
93
+ self.input: List[WorkflowVariableConfig] = input
94
+ self.output: List[WorkflowVariableConfig] = output
@@ -0,0 +1,74 @@
1
+ from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
2
+ from mindor.dsl.schema.controller import ControllerWebUIConfig, ControllerConfig
3
+ from mindor.dsl.schema.component import ComponentConfig
4
+ from mindor.dsl.schema.workflow import WorkflowConfig
5
+ from mindor.core.controller.runner import ControllerRunner
6
+ from mindor.core.services import AsyncService
7
+ from .schema import WorkflowSchema, WorkflowInputResolver, WorkflowOutputResolver
8
+ from .gradio import GradioWebUIBuilder
9
+
10
+ from fastapi import FastAPI, APIRouter, Body, HTTPException
11
+ import asyncio, uvicorn
12
+ from gradio import mount_gradio_app
13
+ import gradio, re
14
+
15
+ class ControllerWebUI(AsyncService):
16
+ def __init__(
17
+ self,
18
+ config: ControllerWebUIConfig,
19
+ controller: ControllerConfig,
20
+ components: Dict[str, ComponentConfig],
21
+ workflows: Dict[str, WorkflowConfig],
22
+ env: Dict[str, str],
23
+ daemon: bool
24
+ ):
25
+ super().__init__(daemon)
26
+
27
+ self.config: ControllerWebUIConfig = config
28
+ self.controller: ControllerConfig = controller
29
+ self.components: Dict[str, ComponentConfig] = components
30
+ self.workflows: Dict[str, WorkflowConfig] = workflows
31
+ self.env: Dict[str, str] = env
32
+ self.server: Optional[uvicorn.Server] = None
33
+ self.app: FastAPI = FastAPI()
34
+ self.runner: ControllerRunner = ControllerRunner(controller)
35
+
36
+ self._configure_driver()
37
+
38
+ def _configure_driver(self) -> None:
39
+ if self.config.driver == "gradio":
40
+ blocks: gradio.Blocks = GradioWebUIBuilder().build(
41
+ schema=self._resolve_workflow_schema(),
42
+ runner=self._run_workflow
43
+ )
44
+ self.app = mount_gradio_app(self.app, blocks, path="")
45
+ return
46
+
47
+ async def _serve(self) -> None:
48
+ self.server = uvicorn.Server(uvicorn.Config(
49
+ self.app,
50
+ host=self.config.host,
51
+ port=self.config.port,
52
+ log_level="info"
53
+ ))
54
+ await self.server.serve()
55
+
56
+ async def _shutdown(self) -> None:
57
+ self.server.should_exit = True
58
+
59
+ async def _run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
60
+ return await self.runner.run_workflow(workflow_id, input)
61
+
62
+ def _resolve_workflow_schema(self) -> Dict[str, WorkflowSchema]:
63
+ schema: Dict[str, WorkflowSchema] = {}
64
+
65
+ for workflow_id, workflow in self.workflows.items():
66
+ schema[workflow_id] = WorkflowSchema(
67
+ name=workflow.name,
68
+ title=workflow.title,
69
+ description=workflow.description,
70
+ input=WorkflowInputResolver().resolve(workflow, self.components),
71
+ output=WorkflowOutputResolver().resolve(workflow, self.components)
72
+ )
73
+
74
+ return schema