hexdag 0.5.0.dev1__py3-none-any.whl

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 (261) hide show
  1. hexdag/__init__.py +116 -0
  2. hexdag/__main__.py +30 -0
  3. hexdag/adapters/executors/__init__.py +5 -0
  4. hexdag/adapters/executors/local_executor.py +316 -0
  5. hexdag/builtin/__init__.py +6 -0
  6. hexdag/builtin/adapters/__init__.py +51 -0
  7. hexdag/builtin/adapters/anthropic/__init__.py +5 -0
  8. hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
  9. hexdag/builtin/adapters/database/__init__.py +6 -0
  10. hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
  11. hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
  12. hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
  13. hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
  14. hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
  15. hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
  16. hexdag/builtin/adapters/local/README.md +59 -0
  17. hexdag/builtin/adapters/local/__init__.py +7 -0
  18. hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
  19. hexdag/builtin/adapters/memory/__init__.py +47 -0
  20. hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
  21. hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
  22. hexdag/builtin/adapters/memory/schemas.py +57 -0
  23. hexdag/builtin/adapters/memory/session_memory.py +178 -0
  24. hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
  25. hexdag/builtin/adapters/memory/state_memory.py +280 -0
  26. hexdag/builtin/adapters/mock/README.md +89 -0
  27. hexdag/builtin/adapters/mock/__init__.py +15 -0
  28. hexdag/builtin/adapters/mock/hexdag.toml +50 -0
  29. hexdag/builtin/adapters/mock/mock_database.py +225 -0
  30. hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
  31. hexdag/builtin/adapters/mock/mock_llm.py +177 -0
  32. hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
  33. hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
  34. hexdag/builtin/adapters/openai/__init__.py +5 -0
  35. hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
  36. hexdag/builtin/adapters/secret/__init__.py +7 -0
  37. hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
  38. hexdag/builtin/adapters/unified_tool_router.py +280 -0
  39. hexdag/builtin/macros/__init__.py +17 -0
  40. hexdag/builtin/macros/conversation_agent.py +390 -0
  41. hexdag/builtin/macros/llm_macro.py +151 -0
  42. hexdag/builtin/macros/reasoning_agent.py +423 -0
  43. hexdag/builtin/macros/tool_macro.py +380 -0
  44. hexdag/builtin/nodes/__init__.py +38 -0
  45. hexdag/builtin/nodes/_discovery.py +123 -0
  46. hexdag/builtin/nodes/agent_node.py +696 -0
  47. hexdag/builtin/nodes/base_node_factory.py +242 -0
  48. hexdag/builtin/nodes/composite_node.py +926 -0
  49. hexdag/builtin/nodes/data_node.py +201 -0
  50. hexdag/builtin/nodes/expression_node.py +487 -0
  51. hexdag/builtin/nodes/function_node.py +454 -0
  52. hexdag/builtin/nodes/llm_node.py +491 -0
  53. hexdag/builtin/nodes/loop_node.py +920 -0
  54. hexdag/builtin/nodes/mapped_input.py +518 -0
  55. hexdag/builtin/nodes/port_call_node.py +269 -0
  56. hexdag/builtin/nodes/tool_call_node.py +195 -0
  57. hexdag/builtin/nodes/tool_utils.py +390 -0
  58. hexdag/builtin/prompts/__init__.py +68 -0
  59. hexdag/builtin/prompts/base.py +422 -0
  60. hexdag/builtin/prompts/chat_prompts.py +303 -0
  61. hexdag/builtin/prompts/error_correction_prompts.py +320 -0
  62. hexdag/builtin/prompts/tool_prompts.py +160 -0
  63. hexdag/builtin/tools/builtin_tools.py +84 -0
  64. hexdag/builtin/tools/database_tools.py +164 -0
  65. hexdag/cli/__init__.py +17 -0
  66. hexdag/cli/__main__.py +7 -0
  67. hexdag/cli/commands/__init__.py +27 -0
  68. hexdag/cli/commands/build_cmd.py +812 -0
  69. hexdag/cli/commands/create_cmd.py +208 -0
  70. hexdag/cli/commands/docs_cmd.py +293 -0
  71. hexdag/cli/commands/generate_types_cmd.py +252 -0
  72. hexdag/cli/commands/init_cmd.py +188 -0
  73. hexdag/cli/commands/pipeline_cmd.py +494 -0
  74. hexdag/cli/commands/plugin_dev_cmd.py +529 -0
  75. hexdag/cli/commands/plugins_cmd.py +441 -0
  76. hexdag/cli/commands/studio_cmd.py +101 -0
  77. hexdag/cli/commands/validate_cmd.py +221 -0
  78. hexdag/cli/main.py +84 -0
  79. hexdag/core/__init__.py +83 -0
  80. hexdag/core/config/__init__.py +20 -0
  81. hexdag/core/config/loader.py +479 -0
  82. hexdag/core/config/models.py +150 -0
  83. hexdag/core/configurable.py +294 -0
  84. hexdag/core/context/__init__.py +37 -0
  85. hexdag/core/context/execution_context.py +378 -0
  86. hexdag/core/docs/__init__.py +26 -0
  87. hexdag/core/docs/extractors.py +678 -0
  88. hexdag/core/docs/generators.py +890 -0
  89. hexdag/core/docs/models.py +120 -0
  90. hexdag/core/domain/__init__.py +10 -0
  91. hexdag/core/domain/dag.py +1225 -0
  92. hexdag/core/exceptions.py +234 -0
  93. hexdag/core/expression_parser.py +569 -0
  94. hexdag/core/logging.py +449 -0
  95. hexdag/core/models/__init__.py +17 -0
  96. hexdag/core/models/base.py +138 -0
  97. hexdag/core/orchestration/__init__.py +46 -0
  98. hexdag/core/orchestration/body_executor.py +481 -0
  99. hexdag/core/orchestration/components/__init__.py +97 -0
  100. hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
  101. hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
  102. hexdag/core/orchestration/components/execution_coordinator.py +360 -0
  103. hexdag/core/orchestration/components/health_check_manager.py +176 -0
  104. hexdag/core/orchestration/components/input_mapper.py +143 -0
  105. hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
  106. hexdag/core/orchestration/components/node_executor.py +377 -0
  107. hexdag/core/orchestration/components/secret_manager.py +202 -0
  108. hexdag/core/orchestration/components/wave_executor.py +158 -0
  109. hexdag/core/orchestration/constants.py +17 -0
  110. hexdag/core/orchestration/events/README.md +312 -0
  111. hexdag/core/orchestration/events/__init__.py +104 -0
  112. hexdag/core/orchestration/events/batching.py +330 -0
  113. hexdag/core/orchestration/events/decorators.py +139 -0
  114. hexdag/core/orchestration/events/events.py +573 -0
  115. hexdag/core/orchestration/events/observers/__init__.py +30 -0
  116. hexdag/core/orchestration/events/observers/core_observers.py +690 -0
  117. hexdag/core/orchestration/events/observers/models.py +111 -0
  118. hexdag/core/orchestration/events/taxonomy.py +269 -0
  119. hexdag/core/orchestration/hook_context.py +237 -0
  120. hexdag/core/orchestration/hooks.py +437 -0
  121. hexdag/core/orchestration/models.py +418 -0
  122. hexdag/core/orchestration/orchestrator.py +910 -0
  123. hexdag/core/orchestration/orchestrator_factory.py +275 -0
  124. hexdag/core/orchestration/port_wrappers.py +327 -0
  125. hexdag/core/orchestration/prompt/__init__.py +32 -0
  126. hexdag/core/orchestration/prompt/template.py +332 -0
  127. hexdag/core/pipeline_builder/__init__.py +21 -0
  128. hexdag/core/pipeline_builder/component_instantiator.py +386 -0
  129. hexdag/core/pipeline_builder/include_tag.py +265 -0
  130. hexdag/core/pipeline_builder/pipeline_config.py +133 -0
  131. hexdag/core/pipeline_builder/py_tag.py +223 -0
  132. hexdag/core/pipeline_builder/tag_discovery.py +268 -0
  133. hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
  134. hexdag/core/pipeline_builder/yaml_validator.py +569 -0
  135. hexdag/core/ports/__init__.py +65 -0
  136. hexdag/core/ports/api_call.py +133 -0
  137. hexdag/core/ports/database.py +489 -0
  138. hexdag/core/ports/embedding.py +215 -0
  139. hexdag/core/ports/executor.py +237 -0
  140. hexdag/core/ports/file_storage.py +117 -0
  141. hexdag/core/ports/healthcheck.py +87 -0
  142. hexdag/core/ports/llm.py +551 -0
  143. hexdag/core/ports/memory.py +70 -0
  144. hexdag/core/ports/observer_manager.py +130 -0
  145. hexdag/core/ports/secret.py +145 -0
  146. hexdag/core/ports/tool_router.py +94 -0
  147. hexdag/core/ports_builder.py +623 -0
  148. hexdag/core/protocols.py +273 -0
  149. hexdag/core/resolver.py +304 -0
  150. hexdag/core/schema/__init__.py +9 -0
  151. hexdag/core/schema/generator.py +742 -0
  152. hexdag/core/secrets.py +242 -0
  153. hexdag/core/types.py +413 -0
  154. hexdag/core/utils/async_warnings.py +206 -0
  155. hexdag/core/utils/schema_conversion.py +78 -0
  156. hexdag/core/utils/sql_validation.py +86 -0
  157. hexdag/core/validation/secure_json.py +148 -0
  158. hexdag/core/yaml_macro.py +517 -0
  159. hexdag/mcp_server.py +3120 -0
  160. hexdag/studio/__init__.py +10 -0
  161. hexdag/studio/build_ui.py +92 -0
  162. hexdag/studio/server/__init__.py +1 -0
  163. hexdag/studio/server/main.py +100 -0
  164. hexdag/studio/server/routes/__init__.py +9 -0
  165. hexdag/studio/server/routes/execute.py +208 -0
  166. hexdag/studio/server/routes/export.py +558 -0
  167. hexdag/studio/server/routes/files.py +207 -0
  168. hexdag/studio/server/routes/plugins.py +419 -0
  169. hexdag/studio/server/routes/validate.py +220 -0
  170. hexdag/studio/ui/index.html +13 -0
  171. hexdag/studio/ui/package-lock.json +2992 -0
  172. hexdag/studio/ui/package.json +31 -0
  173. hexdag/studio/ui/postcss.config.js +6 -0
  174. hexdag/studio/ui/public/hexdag.svg +5 -0
  175. hexdag/studio/ui/src/App.tsx +251 -0
  176. hexdag/studio/ui/src/components/Canvas.tsx +408 -0
  177. hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
  178. hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
  179. hexdag/studio/ui/src/components/Header.tsx +181 -0
  180. hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
  181. hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
  182. hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
  183. hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
  184. hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
  185. hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
  186. hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
  187. hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
  188. hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
  189. hexdag/studio/ui/src/components/index.ts +8 -0
  190. hexdag/studio/ui/src/index.css +92 -0
  191. hexdag/studio/ui/src/main.tsx +10 -0
  192. hexdag/studio/ui/src/types/index.ts +123 -0
  193. hexdag/studio/ui/src/vite-env.d.ts +1 -0
  194. hexdag/studio/ui/tailwind.config.js +29 -0
  195. hexdag/studio/ui/tsconfig.json +37 -0
  196. hexdag/studio/ui/tsconfig.node.json +13 -0
  197. hexdag/studio/ui/vite.config.ts +35 -0
  198. hexdag/visualization/__init__.py +69 -0
  199. hexdag/visualization/dag_visualizer.py +1020 -0
  200. hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
  201. hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
  202. hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
  203. hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
  204. hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
  205. hexdag_plugins/.gitignore +43 -0
  206. hexdag_plugins/README.md +73 -0
  207. hexdag_plugins/__init__.py +1 -0
  208. hexdag_plugins/azure/LICENSE +21 -0
  209. hexdag_plugins/azure/README.md +414 -0
  210. hexdag_plugins/azure/__init__.py +21 -0
  211. hexdag_plugins/azure/azure_blob_adapter.py +450 -0
  212. hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
  213. hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
  214. hexdag_plugins/azure/azure_openai_adapter.py +415 -0
  215. hexdag_plugins/azure/pyproject.toml +107 -0
  216. hexdag_plugins/azure/tests/__init__.py +1 -0
  217. hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
  218. hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
  219. hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
  220. hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
  221. hexdag_plugins/hexdag_etl/README.md +168 -0
  222. hexdag_plugins/hexdag_etl/__init__.py +53 -0
  223. hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
  224. hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
  225. hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
  226. hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
  227. hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
  228. hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
  229. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
  230. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
  231. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
  232. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
  233. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
  234. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
  235. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
  236. hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
  237. hexdag_plugins/hexdag_etl/test_transform.py +54 -0
  238. hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
  239. hexdag_plugins/mysql_adapter/LICENSE +21 -0
  240. hexdag_plugins/mysql_adapter/README.md +224 -0
  241. hexdag_plugins/mysql_adapter/__init__.py +6 -0
  242. hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
  243. hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
  244. hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
  245. hexdag_plugins/storage/README.md +184 -0
  246. hexdag_plugins/storage/__init__.py +19 -0
  247. hexdag_plugins/storage/file/__init__.py +5 -0
  248. hexdag_plugins/storage/file/local.py +325 -0
  249. hexdag_plugins/storage/ports/__init__.py +5 -0
  250. hexdag_plugins/storage/ports/vector_store.py +236 -0
  251. hexdag_plugins/storage/sql/__init__.py +7 -0
  252. hexdag_plugins/storage/sql/base.py +187 -0
  253. hexdag_plugins/storage/sql/mysql.py +27 -0
  254. hexdag_plugins/storage/sql/postgresql.py +27 -0
  255. hexdag_plugins/storage/tests/__init__.py +1 -0
  256. hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
  257. hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
  258. hexdag_plugins/storage/vector/__init__.py +7 -0
  259. hexdag_plugins/storage/vector/chromadb.py +223 -0
  260. hexdag_plugins/storage/vector/in_memory.py +285 -0
  261. hexdag_plugins/storage/vector/pgvector.py +502 -0
@@ -0,0 +1,347 @@
1
+ import { useState, useEffect } from 'react'
2
+ import {
3
+ Plug,
4
+ Package,
5
+ Loader2,
6
+ CheckCircle2,
7
+ XCircle,
8
+ ChevronDown,
9
+ ChevronRight,
10
+ Key,
11
+ Database,
12
+ HardDrive,
13
+ Brain,
14
+ RefreshCw,
15
+ X,
16
+ } from 'lucide-react'
17
+ import { listPlugins, type PluginInfo, type PluginAdapter, type PluginNode } from '../lib/api'
18
+
19
+ interface PluginManagerProps {
20
+ isOpen: boolean
21
+ onClose: () => void
22
+ }
23
+
24
+ export default function PluginManager({ isOpen, onClose }: PluginManagerProps) {
25
+ const [plugins, setPlugins] = useState<PluginInfo[]>([])
26
+ const [isLoading, setIsLoading] = useState(true)
27
+ const [expandedPlugins, setExpandedPlugins] = useState<Set<string>>(new Set())
28
+ const [activeTab, setActiveTab] = useState<'all' | 'adapters' | 'nodes'>('all')
29
+
30
+ useEffect(() => {
31
+ if (isOpen) {
32
+ loadPlugins()
33
+ }
34
+ }, [isOpen])
35
+
36
+ const loadPlugins = async () => {
37
+ try {
38
+ setIsLoading(true)
39
+ const data = await listPlugins()
40
+ setPlugins(data)
41
+ // Expand first plugin by default
42
+ if (data.length > 0) {
43
+ setExpandedPlugins(new Set([data[0].name]))
44
+ }
45
+ } catch (error) {
46
+ console.error('Failed to load plugins:', error)
47
+ } finally {
48
+ setIsLoading(false)
49
+ }
50
+ }
51
+
52
+ const togglePlugin = (name: string) => {
53
+ setExpandedPlugins((prev) => {
54
+ const next = new Set(prev)
55
+ if (next.has(name)) {
56
+ next.delete(name)
57
+ } else {
58
+ next.add(name)
59
+ }
60
+ return next
61
+ })
62
+ }
63
+
64
+ const getAdapterIcon = (adapter: PluginAdapter) => {
65
+ const portType = adapter.port_type.toLowerCase()
66
+ if (portType === 'llm') return Brain
67
+ if (portType === 'secret') return Key
68
+ if (portType === 'memory' || portType === 'database') return Database
69
+ if (portType === 'storage') return HardDrive
70
+ return Package
71
+ }
72
+
73
+ const getPortTypeColor = (portType: string) => {
74
+ const type = portType.toLowerCase()
75
+ if (type === 'llm') return '#6366f1'
76
+ if (type === 'secret') return '#f59e0b'
77
+ if (type === 'memory') return '#14b8a6'
78
+ if (type === 'database') return '#ec4899'
79
+ if (type === 'storage') return '#22c55e'
80
+ return '#6b7280'
81
+ }
82
+
83
+ if (!isOpen) return null
84
+
85
+ return (
86
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
87
+ <div className="bg-hex-surface border border-hex-border rounded-lg shadow-2xl w-[800px] max-h-[80vh] flex flex-col">
88
+ {/* Header */}
89
+ <div className="flex items-center justify-between p-4 border-b border-hex-border">
90
+ <div className="flex items-center gap-3">
91
+ <div className="w-10 h-10 rounded-lg bg-hex-accent/20 flex items-center justify-center">
92
+ <Plug size={20} className="text-hex-accent" />
93
+ </div>
94
+ <div>
95
+ <h2 className="text-lg font-semibold text-hex-text">Plugin Manager</h2>
96
+ <p className="text-xs text-hex-text-muted">
97
+ Manage hexdag plugins and their components
98
+ </p>
99
+ </div>
100
+ </div>
101
+ <div className="flex items-center gap-2">
102
+ <button
103
+ onClick={loadPlugins}
104
+ disabled={isLoading}
105
+ className="p-2 rounded hover:bg-hex-border/50 transition-colors text-hex-text-muted hover:text-hex-text"
106
+ >
107
+ <RefreshCw size={16} className={isLoading ? 'animate-spin' : ''} />
108
+ </button>
109
+ <button
110
+ onClick={onClose}
111
+ className="p-2 rounded hover:bg-hex-border/50 transition-colors text-hex-text-muted hover:text-hex-text"
112
+ >
113
+ <X size={16} />
114
+ </button>
115
+ </div>
116
+ </div>
117
+
118
+ {/* Tabs */}
119
+ <div className="flex border-b border-hex-border px-4">
120
+ {(['all', 'adapters', 'nodes'] as const).map((tab) => (
121
+ <button
122
+ key={tab}
123
+ onClick={() => setActiveTab(tab)}
124
+ className={`px-4 py-2 text-xs font-medium border-b-2 transition-colors ${
125
+ activeTab === tab
126
+ ? 'text-hex-accent border-hex-accent'
127
+ : 'text-hex-text-muted border-transparent hover:text-hex-text'
128
+ }`}
129
+ >
130
+ {tab.charAt(0).toUpperCase() + tab.slice(1)}
131
+ </button>
132
+ ))}
133
+ </div>
134
+
135
+ {/* Content */}
136
+ <div className="flex-1 overflow-y-auto p-4">
137
+ {isLoading ? (
138
+ <div className="flex items-center justify-center h-40">
139
+ <Loader2 size={24} className="animate-spin text-hex-accent" />
140
+ <span className="ml-2 text-hex-text-muted">Loading plugins...</span>
141
+ </div>
142
+ ) : plugins.length === 0 ? (
143
+ <div className="flex flex-col items-center justify-center h-40 text-center">
144
+ <Package size={48} className="text-hex-text-muted opacity-30 mb-4" />
145
+ <p className="text-hex-text-muted">No plugins found</p>
146
+ <p className="text-xs text-hex-text-muted mt-1">
147
+ Add plugins to the hexdag_plugins/ directory
148
+ </p>
149
+ </div>
150
+ ) : (
151
+ <div className="space-y-3">
152
+ {plugins.map((plugin) => (
153
+ <PluginCard
154
+ key={plugin.name}
155
+ plugin={plugin}
156
+ isExpanded={expandedPlugins.has(plugin.name)}
157
+ onToggle={() => togglePlugin(plugin.name)}
158
+ activeTab={activeTab}
159
+ getAdapterIcon={getAdapterIcon}
160
+ getPortTypeColor={getPortTypeColor}
161
+ />
162
+ ))}
163
+ </div>
164
+ )}
165
+ </div>
166
+
167
+ {/* Footer */}
168
+ <div className="px-4 py-3 border-t border-hex-border bg-hex-bg/50">
169
+ <p className="text-[10px] text-hex-text-muted text-center">
170
+ Plugins are automatically discovered from hexdag_plugins/ and installed packages
171
+ </p>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ )
176
+ }
177
+
178
+ interface PluginCardProps {
179
+ plugin: PluginInfo
180
+ isExpanded: boolean
181
+ onToggle: () => void
182
+ activeTab: 'all' | 'adapters' | 'nodes'
183
+ getAdapterIcon: (adapter: PluginAdapter) => typeof Package
184
+ getPortTypeColor: (portType: string) => string
185
+ }
186
+
187
+ function PluginCard({
188
+ plugin,
189
+ isExpanded,
190
+ onToggle,
191
+ activeTab,
192
+ getAdapterIcon,
193
+ getPortTypeColor,
194
+ }: PluginCardProps) {
195
+ const showAdapters = activeTab === 'all' || activeTab === 'adapters'
196
+ const showNodes = activeTab === 'all' || activeTab === 'nodes'
197
+
198
+ return (
199
+ <div className="border border-hex-border rounded-lg overflow-hidden">
200
+ {/* Plugin Header */}
201
+ <button
202
+ onClick={onToggle}
203
+ className="w-full flex items-center gap-3 p-3 hover:bg-hex-border/30 transition-colors"
204
+ >
205
+ {isExpanded ? (
206
+ <ChevronDown size={14} className="text-hex-text-muted" />
207
+ ) : (
208
+ <ChevronRight size={14} className="text-hex-text-muted" />
209
+ )}
210
+ <div className="w-8 h-8 rounded bg-green-500/20 flex items-center justify-center">
211
+ <Plug size={16} className="text-green-500" />
212
+ </div>
213
+ <div className="flex-1 text-left">
214
+ <div className="flex items-center gap-2">
215
+ <span className="text-sm font-medium text-hex-text">{plugin.name}</span>
216
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-hex-border text-hex-text-muted">
217
+ v{plugin.version}
218
+ </span>
219
+ </div>
220
+ <p className="text-xs text-hex-text-muted truncate">{plugin.description}</p>
221
+ </div>
222
+ <div className="flex items-center gap-4 text-hex-text-muted">
223
+ <div className="flex items-center gap-1 text-xs">
224
+ <Key size={12} />
225
+ <span>{plugin.adapters.length}</span>
226
+ </div>
227
+ <div className="flex items-center gap-1 text-xs">
228
+ <Package size={12} />
229
+ <span>{plugin.nodes.length}</span>
230
+ </div>
231
+ {plugin.enabled ? (
232
+ <CheckCircle2 size={16} className="text-green-500" />
233
+ ) : (
234
+ <XCircle size={16} className="text-red-500" />
235
+ )}
236
+ </div>
237
+ </button>
238
+
239
+ {/* Expanded Content */}
240
+ {isExpanded && (
241
+ <div className="border-t border-hex-border bg-hex-bg/50 p-3 space-y-4">
242
+ {/* Adapters */}
243
+ {showAdapters && plugin.adapters.length > 0 && (
244
+ <div>
245
+ <h4 className="text-xs font-medium text-hex-text-muted mb-2 flex items-center gap-2">
246
+ <Key size={12} />
247
+ Adapters ({plugin.adapters.length})
248
+ </h4>
249
+ <div className="grid grid-cols-2 gap-2">
250
+ {plugin.adapters.map((adapter, idx) => {
251
+ const Icon = getAdapterIcon(adapter)
252
+ const color = getPortTypeColor(adapter.port_type)
253
+ return (
254
+ <div
255
+ key={idx}
256
+ className="flex items-start gap-2 p-2 rounded bg-hex-surface border border-hex-border"
257
+ >
258
+ <div
259
+ className="w-7 h-7 rounded flex items-center justify-center flex-shrink-0"
260
+ style={{ backgroundColor: `${color}20` }}
261
+ >
262
+ <Icon size={14} style={{ color }} />
263
+ </div>
264
+ <div className="flex-1 min-w-0">
265
+ <div className="text-xs font-medium text-hex-text truncate">
266
+ {adapter.name}
267
+ </div>
268
+ <div className="text-[10px] text-hex-text-muted truncate">
269
+ {adapter.description}
270
+ </div>
271
+ <div className="flex items-center gap-2 mt-1">
272
+ <span
273
+ className="text-[9px] px-1 py-0.5 rounded"
274
+ style={{
275
+ backgroundColor: `${color}20`,
276
+ color: color,
277
+ }}
278
+ >
279
+ {adapter.port_type}
280
+ </span>
281
+ {adapter.secrets.length > 0 && (
282
+ <span className="text-[9px] px-1 py-0.5 rounded bg-amber-500/20 text-amber-500">
283
+ {adapter.secrets.length} secrets
284
+ </span>
285
+ )}
286
+ </div>
287
+ </div>
288
+ </div>
289
+ )
290
+ })}
291
+ </div>
292
+ </div>
293
+ )}
294
+
295
+ {/* Nodes */}
296
+ {showNodes && plugin.nodes.length > 0 && (
297
+ <div>
298
+ <h4 className="text-xs font-medium text-hex-text-muted mb-2 flex items-center gap-2">
299
+ <Package size={12} />
300
+ Nodes ({plugin.nodes.length})
301
+ </h4>
302
+ <div className="grid grid-cols-2 gap-2">
303
+ {plugin.nodes.map((node, idx) => (
304
+ <NodeCard key={idx} node={node} />
305
+ ))}
306
+ </div>
307
+ </div>
308
+ )}
309
+
310
+ {/* No content message */}
311
+ {((activeTab === 'adapters' && plugin.adapters.length === 0) ||
312
+ (activeTab === 'nodes' && plugin.nodes.length === 0)) && (
313
+ <p className="text-xs text-hex-text-muted text-center py-4">
314
+ No {activeTab} in this plugin
315
+ </p>
316
+ )}
317
+ </div>
318
+ )}
319
+ </div>
320
+ )
321
+ }
322
+
323
+ function NodeCard({ node }: { node: PluginNode }) {
324
+ return (
325
+ <div className="flex items-start gap-2 p-2 rounded bg-hex-surface border border-hex-border">
326
+ <div
327
+ className="w-7 h-7 rounded flex items-center justify-center flex-shrink-0"
328
+ style={{ backgroundColor: `${node.color}20` }}
329
+ >
330
+ <Package size={14} style={{ color: node.color }} />
331
+ </div>
332
+ <div className="flex-1 min-w-0">
333
+ <div className="text-xs font-medium text-hex-text truncate">{node.name}</div>
334
+ <div className="text-[10px] text-hex-text-muted truncate">{node.description}</div>
335
+ <span
336
+ className="inline-block text-[9px] px-1 py-0.5 rounded mt-1"
337
+ style={{
338
+ backgroundColor: `${node.color}20`,
339
+ color: node.color,
340
+ }}
341
+ >
342
+ {node.kind}
343
+ </span>
344
+ </div>
345
+ </div>
346
+ )
347
+ }