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.
- hexdag/__init__.py +116 -0
- hexdag/__main__.py +30 -0
- hexdag/adapters/executors/__init__.py +5 -0
- hexdag/adapters/executors/local_executor.py +316 -0
- hexdag/builtin/__init__.py +6 -0
- hexdag/builtin/adapters/__init__.py +51 -0
- hexdag/builtin/adapters/anthropic/__init__.py +5 -0
- hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
- hexdag/builtin/adapters/database/__init__.py +6 -0
- hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
- hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
- hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
- hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
- hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
- hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
- hexdag/builtin/adapters/local/README.md +59 -0
- hexdag/builtin/adapters/local/__init__.py +7 -0
- hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
- hexdag/builtin/adapters/memory/__init__.py +47 -0
- hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
- hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
- hexdag/builtin/adapters/memory/schemas.py +57 -0
- hexdag/builtin/adapters/memory/session_memory.py +178 -0
- hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
- hexdag/builtin/adapters/memory/state_memory.py +280 -0
- hexdag/builtin/adapters/mock/README.md +89 -0
- hexdag/builtin/adapters/mock/__init__.py +15 -0
- hexdag/builtin/adapters/mock/hexdag.toml +50 -0
- hexdag/builtin/adapters/mock/mock_database.py +225 -0
- hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
- hexdag/builtin/adapters/mock/mock_llm.py +177 -0
- hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
- hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
- hexdag/builtin/adapters/openai/__init__.py +5 -0
- hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
- hexdag/builtin/adapters/secret/__init__.py +7 -0
- hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
- hexdag/builtin/adapters/unified_tool_router.py +280 -0
- hexdag/builtin/macros/__init__.py +17 -0
- hexdag/builtin/macros/conversation_agent.py +390 -0
- hexdag/builtin/macros/llm_macro.py +151 -0
- hexdag/builtin/macros/reasoning_agent.py +423 -0
- hexdag/builtin/macros/tool_macro.py +380 -0
- hexdag/builtin/nodes/__init__.py +38 -0
- hexdag/builtin/nodes/_discovery.py +123 -0
- hexdag/builtin/nodes/agent_node.py +696 -0
- hexdag/builtin/nodes/base_node_factory.py +242 -0
- hexdag/builtin/nodes/composite_node.py +926 -0
- hexdag/builtin/nodes/data_node.py +201 -0
- hexdag/builtin/nodes/expression_node.py +487 -0
- hexdag/builtin/nodes/function_node.py +454 -0
- hexdag/builtin/nodes/llm_node.py +491 -0
- hexdag/builtin/nodes/loop_node.py +920 -0
- hexdag/builtin/nodes/mapped_input.py +518 -0
- hexdag/builtin/nodes/port_call_node.py +269 -0
- hexdag/builtin/nodes/tool_call_node.py +195 -0
- hexdag/builtin/nodes/tool_utils.py +390 -0
- hexdag/builtin/prompts/__init__.py +68 -0
- hexdag/builtin/prompts/base.py +422 -0
- hexdag/builtin/prompts/chat_prompts.py +303 -0
- hexdag/builtin/prompts/error_correction_prompts.py +320 -0
- hexdag/builtin/prompts/tool_prompts.py +160 -0
- hexdag/builtin/tools/builtin_tools.py +84 -0
- hexdag/builtin/tools/database_tools.py +164 -0
- hexdag/cli/__init__.py +17 -0
- hexdag/cli/__main__.py +7 -0
- hexdag/cli/commands/__init__.py +27 -0
- hexdag/cli/commands/build_cmd.py +812 -0
- hexdag/cli/commands/create_cmd.py +208 -0
- hexdag/cli/commands/docs_cmd.py +293 -0
- hexdag/cli/commands/generate_types_cmd.py +252 -0
- hexdag/cli/commands/init_cmd.py +188 -0
- hexdag/cli/commands/pipeline_cmd.py +494 -0
- hexdag/cli/commands/plugin_dev_cmd.py +529 -0
- hexdag/cli/commands/plugins_cmd.py +441 -0
- hexdag/cli/commands/studio_cmd.py +101 -0
- hexdag/cli/commands/validate_cmd.py +221 -0
- hexdag/cli/main.py +84 -0
- hexdag/core/__init__.py +83 -0
- hexdag/core/config/__init__.py +20 -0
- hexdag/core/config/loader.py +479 -0
- hexdag/core/config/models.py +150 -0
- hexdag/core/configurable.py +294 -0
- hexdag/core/context/__init__.py +37 -0
- hexdag/core/context/execution_context.py +378 -0
- hexdag/core/docs/__init__.py +26 -0
- hexdag/core/docs/extractors.py +678 -0
- hexdag/core/docs/generators.py +890 -0
- hexdag/core/docs/models.py +120 -0
- hexdag/core/domain/__init__.py +10 -0
- hexdag/core/domain/dag.py +1225 -0
- hexdag/core/exceptions.py +234 -0
- hexdag/core/expression_parser.py +569 -0
- hexdag/core/logging.py +449 -0
- hexdag/core/models/__init__.py +17 -0
- hexdag/core/models/base.py +138 -0
- hexdag/core/orchestration/__init__.py +46 -0
- hexdag/core/orchestration/body_executor.py +481 -0
- hexdag/core/orchestration/components/__init__.py +97 -0
- hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
- hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
- hexdag/core/orchestration/components/execution_coordinator.py +360 -0
- hexdag/core/orchestration/components/health_check_manager.py +176 -0
- hexdag/core/orchestration/components/input_mapper.py +143 -0
- hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
- hexdag/core/orchestration/components/node_executor.py +377 -0
- hexdag/core/orchestration/components/secret_manager.py +202 -0
- hexdag/core/orchestration/components/wave_executor.py +158 -0
- hexdag/core/orchestration/constants.py +17 -0
- hexdag/core/orchestration/events/README.md +312 -0
- hexdag/core/orchestration/events/__init__.py +104 -0
- hexdag/core/orchestration/events/batching.py +330 -0
- hexdag/core/orchestration/events/decorators.py +139 -0
- hexdag/core/orchestration/events/events.py +573 -0
- hexdag/core/orchestration/events/observers/__init__.py +30 -0
- hexdag/core/orchestration/events/observers/core_observers.py +690 -0
- hexdag/core/orchestration/events/observers/models.py +111 -0
- hexdag/core/orchestration/events/taxonomy.py +269 -0
- hexdag/core/orchestration/hook_context.py +237 -0
- hexdag/core/orchestration/hooks.py +437 -0
- hexdag/core/orchestration/models.py +418 -0
- hexdag/core/orchestration/orchestrator.py +910 -0
- hexdag/core/orchestration/orchestrator_factory.py +275 -0
- hexdag/core/orchestration/port_wrappers.py +327 -0
- hexdag/core/orchestration/prompt/__init__.py +32 -0
- hexdag/core/orchestration/prompt/template.py +332 -0
- hexdag/core/pipeline_builder/__init__.py +21 -0
- hexdag/core/pipeline_builder/component_instantiator.py +386 -0
- hexdag/core/pipeline_builder/include_tag.py +265 -0
- hexdag/core/pipeline_builder/pipeline_config.py +133 -0
- hexdag/core/pipeline_builder/py_tag.py +223 -0
- hexdag/core/pipeline_builder/tag_discovery.py +268 -0
- hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
- hexdag/core/pipeline_builder/yaml_validator.py +569 -0
- hexdag/core/ports/__init__.py +65 -0
- hexdag/core/ports/api_call.py +133 -0
- hexdag/core/ports/database.py +489 -0
- hexdag/core/ports/embedding.py +215 -0
- hexdag/core/ports/executor.py +237 -0
- hexdag/core/ports/file_storage.py +117 -0
- hexdag/core/ports/healthcheck.py +87 -0
- hexdag/core/ports/llm.py +551 -0
- hexdag/core/ports/memory.py +70 -0
- hexdag/core/ports/observer_manager.py +130 -0
- hexdag/core/ports/secret.py +145 -0
- hexdag/core/ports/tool_router.py +94 -0
- hexdag/core/ports_builder.py +623 -0
- hexdag/core/protocols.py +273 -0
- hexdag/core/resolver.py +304 -0
- hexdag/core/schema/__init__.py +9 -0
- hexdag/core/schema/generator.py +742 -0
- hexdag/core/secrets.py +242 -0
- hexdag/core/types.py +413 -0
- hexdag/core/utils/async_warnings.py +206 -0
- hexdag/core/utils/schema_conversion.py +78 -0
- hexdag/core/utils/sql_validation.py +86 -0
- hexdag/core/validation/secure_json.py +148 -0
- hexdag/core/yaml_macro.py +517 -0
- hexdag/mcp_server.py +3120 -0
- hexdag/studio/__init__.py +10 -0
- hexdag/studio/build_ui.py +92 -0
- hexdag/studio/server/__init__.py +1 -0
- hexdag/studio/server/main.py +100 -0
- hexdag/studio/server/routes/__init__.py +9 -0
- hexdag/studio/server/routes/execute.py +208 -0
- hexdag/studio/server/routes/export.py +558 -0
- hexdag/studio/server/routes/files.py +207 -0
- hexdag/studio/server/routes/plugins.py +419 -0
- hexdag/studio/server/routes/validate.py +220 -0
- hexdag/studio/ui/index.html +13 -0
- hexdag/studio/ui/package-lock.json +2992 -0
- hexdag/studio/ui/package.json +31 -0
- hexdag/studio/ui/postcss.config.js +6 -0
- hexdag/studio/ui/public/hexdag.svg +5 -0
- hexdag/studio/ui/src/App.tsx +251 -0
- hexdag/studio/ui/src/components/Canvas.tsx +408 -0
- hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
- hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
- hexdag/studio/ui/src/components/Header.tsx +181 -0
- hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
- hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
- hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
- hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
- hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
- hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
- hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
- hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
- hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
- hexdag/studio/ui/src/components/index.ts +8 -0
- hexdag/studio/ui/src/index.css +92 -0
- hexdag/studio/ui/src/main.tsx +10 -0
- hexdag/studio/ui/src/types/index.ts +123 -0
- hexdag/studio/ui/src/vite-env.d.ts +1 -0
- hexdag/studio/ui/tailwind.config.js +29 -0
- hexdag/studio/ui/tsconfig.json +37 -0
- hexdag/studio/ui/tsconfig.node.json +13 -0
- hexdag/studio/ui/vite.config.ts +35 -0
- hexdag/visualization/__init__.py +69 -0
- hexdag/visualization/dag_visualizer.py +1020 -0
- hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
- hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
- hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
- hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
- hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
- hexdag_plugins/.gitignore +43 -0
- hexdag_plugins/README.md +73 -0
- hexdag_plugins/__init__.py +1 -0
- hexdag_plugins/azure/LICENSE +21 -0
- hexdag_plugins/azure/README.md +414 -0
- hexdag_plugins/azure/__init__.py +21 -0
- hexdag_plugins/azure/azure_blob_adapter.py +450 -0
- hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
- hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
- hexdag_plugins/azure/azure_openai_adapter.py +415 -0
- hexdag_plugins/azure/pyproject.toml +107 -0
- hexdag_plugins/azure/tests/__init__.py +1 -0
- hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
- hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
- hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
- hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
- hexdag_plugins/hexdag_etl/README.md +168 -0
- hexdag_plugins/hexdag_etl/__init__.py +53 -0
- hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
- hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
- hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
- hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
- hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
- hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
- hexdag_plugins/hexdag_etl/test_transform.py +54 -0
- hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
- hexdag_plugins/mysql_adapter/LICENSE +21 -0
- hexdag_plugins/mysql_adapter/README.md +224 -0
- hexdag_plugins/mysql_adapter/__init__.py +6 -0
- hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
- hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
- hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
- hexdag_plugins/storage/README.md +184 -0
- hexdag_plugins/storage/__init__.py +19 -0
- hexdag_plugins/storage/file/__init__.py +5 -0
- hexdag_plugins/storage/file/local.py +325 -0
- hexdag_plugins/storage/ports/__init__.py +5 -0
- hexdag_plugins/storage/ports/vector_store.py +236 -0
- hexdag_plugins/storage/sql/__init__.py +7 -0
- hexdag_plugins/storage/sql/base.py +187 -0
- hexdag_plugins/storage/sql/mysql.py +27 -0
- hexdag_plugins/storage/sql/postgresql.py +27 -0
- hexdag_plugins/storage/tests/__init__.py +1 -0
- hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
- hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
- hexdag_plugins/storage/vector/__init__.py +7 -0
- hexdag_plugins/storage/vector/chromadb.py +223 -0
- hexdag_plugins/storage/vector/in_memory.py +285 -0
- hexdag_plugins/storage/vector/pgvector.py +502 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Brain,
|
|
4
|
+
Key,
|
|
5
|
+
Database,
|
|
6
|
+
HardDrive,
|
|
7
|
+
Plug,
|
|
8
|
+
ChevronDown,
|
|
9
|
+
ChevronRight,
|
|
10
|
+
Plus,
|
|
11
|
+
X,
|
|
12
|
+
Loader2,
|
|
13
|
+
Info,
|
|
14
|
+
} from 'lucide-react'
|
|
15
|
+
import { useStudioStore } from '../lib/store'
|
|
16
|
+
import { getAllPluginAdapters, type PluginAdapter } from '../lib/api'
|
|
17
|
+
import yaml from 'yaml'
|
|
18
|
+
|
|
19
|
+
interface PortConfig {
|
|
20
|
+
adapter: string
|
|
21
|
+
config: Record<string, unknown>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface PortsConfig {
|
|
25
|
+
llm?: PortConfig
|
|
26
|
+
memory?: PortConfig
|
|
27
|
+
database?: PortConfig
|
|
28
|
+
storage?: PortConfig
|
|
29
|
+
secret?: PortConfig
|
|
30
|
+
[key: string]: PortConfig | undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const PORT_TYPES = ['llm', 'memory', 'database', 'storage', 'secret'] as const
|
|
34
|
+
|
|
35
|
+
const PORT_INFO: Record<string, { icon: typeof Brain; color: string; description: string }> = {
|
|
36
|
+
llm: {
|
|
37
|
+
icon: Brain,
|
|
38
|
+
color: '#6366f1',
|
|
39
|
+
description: 'Language model for AI inference (OpenAI, Azure OpenAI, Anthropic)',
|
|
40
|
+
},
|
|
41
|
+
memory: {
|
|
42
|
+
icon: Database,
|
|
43
|
+
color: '#14b8a6',
|
|
44
|
+
description: 'Persistent memory for agent state and conversations',
|
|
45
|
+
},
|
|
46
|
+
database: {
|
|
47
|
+
icon: Database,
|
|
48
|
+
color: '#ec4899',
|
|
49
|
+
description: 'Database connection for data persistence',
|
|
50
|
+
},
|
|
51
|
+
storage: {
|
|
52
|
+
icon: HardDrive,
|
|
53
|
+
color: '#22c55e',
|
|
54
|
+
description: 'File storage for documents and artifacts',
|
|
55
|
+
},
|
|
56
|
+
secret: {
|
|
57
|
+
icon: Key,
|
|
58
|
+
color: '#f59e0b',
|
|
59
|
+
description: 'Secret management for API keys and credentials',
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default function PortsEditor() {
|
|
64
|
+
const { yamlContent, setYamlContent, setIsDirty } = useStudioStore()
|
|
65
|
+
const [ports, setPorts] = useState<PortsConfig>({})
|
|
66
|
+
const [adapters, setAdapters] = useState<PluginAdapter[]>([])
|
|
67
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
68
|
+
const [expandedPorts, setExpandedPorts] = useState<Set<string>>(new Set(['llm']))
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
loadAdapters()
|
|
72
|
+
}, [])
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
parsePortsFromYaml()
|
|
76
|
+
}, [yamlContent])
|
|
77
|
+
|
|
78
|
+
const loadAdapters = async () => {
|
|
79
|
+
try {
|
|
80
|
+
setIsLoading(true)
|
|
81
|
+
const data = await getAllPluginAdapters()
|
|
82
|
+
setAdapters(data)
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Failed to load adapters:', error)
|
|
85
|
+
} finally {
|
|
86
|
+
setIsLoading(false)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parsePortsFromYaml = () => {
|
|
91
|
+
try {
|
|
92
|
+
if (!yamlContent) {
|
|
93
|
+
setPorts({})
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
const parsed = yaml.parse(yamlContent) as { spec?: { ports?: PortsConfig } }
|
|
97
|
+
setPorts(parsed?.spec?.ports || {})
|
|
98
|
+
} catch {
|
|
99
|
+
setPorts({})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const updatePortsInYaml = (newPorts: PortsConfig) => {
|
|
104
|
+
try {
|
|
105
|
+
// Parse existing YAML or create default structure
|
|
106
|
+
let parsed: Record<string, unknown> | null = null
|
|
107
|
+
try {
|
|
108
|
+
parsed = yamlContent ? yaml.parse(yamlContent) as Record<string, unknown> : null
|
|
109
|
+
} catch {
|
|
110
|
+
parsed = null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create default pipeline structure if needed
|
|
114
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
115
|
+
parsed = {
|
|
116
|
+
apiVersion: 'hexdag/v1',
|
|
117
|
+
kind: 'Pipeline',
|
|
118
|
+
metadata: { name: 'untitled' },
|
|
119
|
+
spec: { nodes: [] },
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Ensure spec exists
|
|
124
|
+
if (!parsed.spec || typeof parsed.spec !== 'object') {
|
|
125
|
+
parsed.spec = { nodes: [] }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const spec = parsed.spec as { ports?: PortsConfig; nodes?: unknown[] }
|
|
129
|
+
|
|
130
|
+
// Ensure nodes array exists
|
|
131
|
+
if (!spec.nodes) {
|
|
132
|
+
spec.nodes = []
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Remove empty ports
|
|
136
|
+
const cleanedPorts: PortsConfig = {}
|
|
137
|
+
for (const [key, value] of Object.entries(newPorts)) {
|
|
138
|
+
if (value && value.adapter) {
|
|
139
|
+
cleanedPorts[key] = value
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (Object.keys(cleanedPorts).length > 0) {
|
|
144
|
+
spec.ports = cleanedPorts
|
|
145
|
+
} else {
|
|
146
|
+
delete spec.ports
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const newYaml = yaml.stringify(parsed, {
|
|
150
|
+
indent: 2,
|
|
151
|
+
lineWidth: 0,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
setYamlContent(newYaml)
|
|
155
|
+
setIsDirty(true)
|
|
156
|
+
setPorts(cleanedPorts)
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Failed to update YAML:', error)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const togglePort = (portType: string) => {
|
|
163
|
+
setExpandedPorts((prev) => {
|
|
164
|
+
const next = new Set(prev)
|
|
165
|
+
if (next.has(portType)) {
|
|
166
|
+
next.delete(portType)
|
|
167
|
+
} else {
|
|
168
|
+
next.add(portType)
|
|
169
|
+
}
|
|
170
|
+
return next
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const setPortAdapter = (portType: string, adapterName: string) => {
|
|
175
|
+
const adapter = adapters.find((a) => a.name === adapterName)
|
|
176
|
+
const newPorts = { ...ports }
|
|
177
|
+
|
|
178
|
+
if (!adapterName) {
|
|
179
|
+
delete newPorts[portType]
|
|
180
|
+
} else {
|
|
181
|
+
newPorts[portType] = {
|
|
182
|
+
adapter: adapterName,
|
|
183
|
+
config: adapter ? getDefaultConfig(adapter) : {},
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
updatePortsInYaml(newPorts)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const updatePortConfig = (portType: string, key: string, value: unknown) => {
|
|
191
|
+
const newPorts = { ...ports }
|
|
192
|
+
if (newPorts[portType]) {
|
|
193
|
+
newPorts[portType] = {
|
|
194
|
+
...newPorts[portType]!,
|
|
195
|
+
config: {
|
|
196
|
+
...newPorts[portType]!.config,
|
|
197
|
+
[key]: value,
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
updatePortsInYaml(newPorts)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const deletePortConfig = (portType: string, key: string) => {
|
|
205
|
+
const newPorts = { ...ports }
|
|
206
|
+
if (newPorts[portType]) {
|
|
207
|
+
const newConfig = { ...newPorts[portType]!.config }
|
|
208
|
+
delete newConfig[key]
|
|
209
|
+
newPorts[portType] = {
|
|
210
|
+
...newPorts[portType]!,
|
|
211
|
+
config: newConfig,
|
|
212
|
+
}
|
|
213
|
+
updatePortsInYaml(newPorts)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const getDefaultConfig = (adapter: PluginAdapter): Record<string, unknown> => {
|
|
218
|
+
const config: Record<string, unknown> = {}
|
|
219
|
+
const schema = adapter.config_schema as {
|
|
220
|
+
properties?: Record<string, { default?: unknown }>
|
|
221
|
+
required?: string[]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (schema?.properties) {
|
|
225
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
226
|
+
if (prop.default !== undefined && prop.default !== null) {
|
|
227
|
+
config[key] = prop.default
|
|
228
|
+
} else if (schema.required?.includes(key)) {
|
|
229
|
+
// Add placeholder for required fields
|
|
230
|
+
config[key] = `\${${key.toUpperCase()}}`
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return config
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const getAdaptersForPort = (portType: string) => {
|
|
239
|
+
return adapters.filter((a) => a.port_type === portType)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (isLoading) {
|
|
243
|
+
return (
|
|
244
|
+
<div className="h-full flex items-center justify-center">
|
|
245
|
+
<Loader2 size={24} className="animate-spin text-hex-text-muted" />
|
|
246
|
+
</div>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div className="h-full flex flex-col bg-hex-surface">
|
|
252
|
+
{/* Header */}
|
|
253
|
+
<div className="p-3 border-b border-hex-border">
|
|
254
|
+
<div className="flex items-center gap-2">
|
|
255
|
+
<div className="w-8 h-8 rounded bg-hex-accent/20 flex items-center justify-center">
|
|
256
|
+
<Plug size={16} className="text-hex-accent" />
|
|
257
|
+
</div>
|
|
258
|
+
<div>
|
|
259
|
+
<h3 className="text-sm font-medium text-hex-text">Pipeline Ports</h3>
|
|
260
|
+
<p className="text-[10px] text-hex-text-muted">Configure adapters for your pipeline</p>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
{/* Port List */}
|
|
266
|
+
<div className="flex-1 overflow-y-auto">
|
|
267
|
+
{PORT_TYPES.map((portType) => {
|
|
268
|
+
const portInfo = PORT_INFO[portType]
|
|
269
|
+
const Icon = portInfo.icon
|
|
270
|
+
const availableAdapters = getAdaptersForPort(portType)
|
|
271
|
+
const currentPort = ports[portType]
|
|
272
|
+
const isExpanded = expandedPorts.has(portType)
|
|
273
|
+
const isConfigured = !!currentPort?.adapter
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<div key={portType} className="border-b border-hex-border">
|
|
277
|
+
<button
|
|
278
|
+
onClick={() => togglePort(portType)}
|
|
279
|
+
className="w-full flex items-center gap-2 p-3 hover:bg-hex-border/30 transition-colors"
|
|
280
|
+
>
|
|
281
|
+
{isExpanded ? (
|
|
282
|
+
<ChevronDown size={14} className="text-hex-text-muted" />
|
|
283
|
+
) : (
|
|
284
|
+
<ChevronRight size={14} className="text-hex-text-muted" />
|
|
285
|
+
)}
|
|
286
|
+
<div
|
|
287
|
+
className="w-6 h-6 rounded flex items-center justify-center"
|
|
288
|
+
style={{ backgroundColor: `${portInfo.color}20` }}
|
|
289
|
+
>
|
|
290
|
+
<Icon size={12} style={{ color: portInfo.color }} />
|
|
291
|
+
</div>
|
|
292
|
+
<span className="flex-1 text-left text-xs font-medium text-hex-text capitalize">
|
|
293
|
+
{portType}
|
|
294
|
+
</span>
|
|
295
|
+
{isConfigured ? (
|
|
296
|
+
<span
|
|
297
|
+
className="text-[10px] px-1.5 py-0.5 rounded"
|
|
298
|
+
style={{ backgroundColor: `${portInfo.color}20`, color: portInfo.color }}
|
|
299
|
+
>
|
|
300
|
+
{currentPort.adapter}
|
|
301
|
+
</span>
|
|
302
|
+
) : (
|
|
303
|
+
<span className="text-[10px] text-hex-text-muted">Not configured</span>
|
|
304
|
+
)}
|
|
305
|
+
</button>
|
|
306
|
+
|
|
307
|
+
{isExpanded && (
|
|
308
|
+
<div className="px-3 pb-3 space-y-3">
|
|
309
|
+
{/* Description */}
|
|
310
|
+
<div className="flex items-start gap-2 p-2 bg-hex-bg/50 rounded text-[10px] text-hex-text-muted">
|
|
311
|
+
<Info size={12} className="flex-shrink-0 mt-0.5" />
|
|
312
|
+
<span>{portInfo.description}</span>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
{/* Adapter Selection */}
|
|
316
|
+
<div>
|
|
317
|
+
<label className="block text-[10px] font-medium text-hex-text-muted mb-1 uppercase tracking-wider">
|
|
318
|
+
Adapter
|
|
319
|
+
</label>
|
|
320
|
+
{availableAdapters.length === 0 ? (
|
|
321
|
+
<p className="text-[10px] text-hex-text-muted italic">
|
|
322
|
+
No adapters available for this port type
|
|
323
|
+
</p>
|
|
324
|
+
) : (
|
|
325
|
+
<select
|
|
326
|
+
value={currentPort?.adapter || ''}
|
|
327
|
+
onChange={(e) => setPortAdapter(portType, e.target.value)}
|
|
328
|
+
className="w-full bg-hex-bg border border-hex-border rounded px-2 py-1.5 text-xs text-hex-text focus:border-hex-accent focus:outline-none"
|
|
329
|
+
>
|
|
330
|
+
<option value="">-- Select adapter --</option>
|
|
331
|
+
{availableAdapters.map((adapter) => (
|
|
332
|
+
<option key={adapter.name} value={adapter.name}>
|
|
333
|
+
{adapter.name} ({adapter.plugin})
|
|
334
|
+
</option>
|
|
335
|
+
))}
|
|
336
|
+
</select>
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
{/* Adapter Config */}
|
|
341
|
+
{currentPort && currentPort.config && (
|
|
342
|
+
<div className="space-y-2">
|
|
343
|
+
<label className="block text-[10px] font-medium text-hex-text-muted uppercase tracking-wider">
|
|
344
|
+
Configuration
|
|
345
|
+
</label>
|
|
346
|
+
{Object.entries(currentPort.config).map(([key, value]) => (
|
|
347
|
+
<ConfigField
|
|
348
|
+
key={key}
|
|
349
|
+
name={key}
|
|
350
|
+
value={value}
|
|
351
|
+
onChange={(v) => updatePortConfig(portType, key, v)}
|
|
352
|
+
onDelete={() => deletePortConfig(portType, key)}
|
|
353
|
+
/>
|
|
354
|
+
))}
|
|
355
|
+
<AddConfigButton
|
|
356
|
+
onAdd={(key) => updatePortConfig(portType, key, '')}
|
|
357
|
+
existingKeys={Object.keys(currentPort.config)}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
</div>
|
|
364
|
+
)
|
|
365
|
+
})}
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
{/* Footer */}
|
|
369
|
+
<div className="p-3 border-t border-hex-border bg-hex-bg/50">
|
|
370
|
+
<p className="text-[9px] text-hex-text-muted text-center">
|
|
371
|
+
Ports connect your pipeline to external services like LLMs and databases
|
|
372
|
+
</p>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function ConfigField({
|
|
379
|
+
name,
|
|
380
|
+
value,
|
|
381
|
+
onChange,
|
|
382
|
+
onDelete,
|
|
383
|
+
}: {
|
|
384
|
+
name: string
|
|
385
|
+
value: unknown
|
|
386
|
+
onChange: (value: unknown) => void
|
|
387
|
+
onDelete: () => void
|
|
388
|
+
}) {
|
|
389
|
+
const isSecret = name.toLowerCase().includes('key') ||
|
|
390
|
+
name.toLowerCase().includes('secret') ||
|
|
391
|
+
name.toLowerCase().includes('password') ||
|
|
392
|
+
name.toLowerCase().includes('token')
|
|
393
|
+
|
|
394
|
+
const isEnvVar = typeof value === 'string' && value.startsWith('${') && value.endsWith('}')
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<div className="group flex items-start gap-2">
|
|
398
|
+
<div className="flex-1">
|
|
399
|
+
<div className="flex items-center justify-between mb-1">
|
|
400
|
+
<label className="text-[10px] text-hex-text-muted">{name}</label>
|
|
401
|
+
<button
|
|
402
|
+
onClick={onDelete}
|
|
403
|
+
className="opacity-0 group-hover:opacity-100 p-0.5 hover:bg-hex-error/20 rounded transition-all"
|
|
404
|
+
>
|
|
405
|
+
<X size={10} className="text-hex-error" />
|
|
406
|
+
</button>
|
|
407
|
+
</div>
|
|
408
|
+
<div className="relative">
|
|
409
|
+
<input
|
|
410
|
+
type={isSecret && !isEnvVar ? 'password' : 'text'}
|
|
411
|
+
value={String(value)}
|
|
412
|
+
onChange={(e) => onChange(e.target.value)}
|
|
413
|
+
className="w-full bg-hex-bg border border-hex-border rounded px-2 py-1 text-[11px] text-hex-text focus:border-hex-accent focus:outline-none font-mono"
|
|
414
|
+
placeholder={isSecret ? '${ENV_VAR_NAME}' : ''}
|
|
415
|
+
/>
|
|
416
|
+
{isEnvVar && (
|
|
417
|
+
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-[9px] px-1 py-0.5 rounded bg-amber-500/20 text-amber-500">
|
|
418
|
+
env
|
|
419
|
+
</span>
|
|
420
|
+
)}
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function AddConfigButton({
|
|
428
|
+
onAdd,
|
|
429
|
+
existingKeys,
|
|
430
|
+
}: {
|
|
431
|
+
onAdd: (key: string) => void
|
|
432
|
+
existingKeys: string[]
|
|
433
|
+
}) {
|
|
434
|
+
const [isAdding, setIsAdding] = useState(false)
|
|
435
|
+
const [newKey, setNewKey] = useState('')
|
|
436
|
+
|
|
437
|
+
const handleAdd = () => {
|
|
438
|
+
if (!newKey || existingKeys.includes(newKey)) return
|
|
439
|
+
onAdd(newKey)
|
|
440
|
+
setNewKey('')
|
|
441
|
+
setIsAdding(false)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (isAdding) {
|
|
445
|
+
return (
|
|
446
|
+
<div className="flex gap-1">
|
|
447
|
+
<input
|
|
448
|
+
type="text"
|
|
449
|
+
value={newKey}
|
|
450
|
+
onChange={(e) => setNewKey(e.target.value)}
|
|
451
|
+
onKeyDown={(e) => e.key === 'Enter' && handleAdd()}
|
|
452
|
+
placeholder="Config key"
|
|
453
|
+
autoFocus
|
|
454
|
+
className="flex-1 bg-hex-bg border border-hex-border rounded px-2 py-1 text-[11px] text-hex-text focus:border-hex-accent focus:outline-none"
|
|
455
|
+
/>
|
|
456
|
+
<button
|
|
457
|
+
onClick={handleAdd}
|
|
458
|
+
className="px-2 py-1 text-[10px] bg-hex-accent text-white rounded hover:bg-hex-accent-hover"
|
|
459
|
+
>
|
|
460
|
+
Add
|
|
461
|
+
</button>
|
|
462
|
+
<button
|
|
463
|
+
onClick={() => setIsAdding(false)}
|
|
464
|
+
className="px-2 py-1 text-[10px] bg-hex-border text-hex-text rounded"
|
|
465
|
+
>
|
|
466
|
+
Cancel
|
|
467
|
+
</button>
|
|
468
|
+
</div>
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return (
|
|
473
|
+
<button
|
|
474
|
+
onClick={() => setIsAdding(true)}
|
|
475
|
+
className="w-full flex items-center justify-center gap-1 py-1 text-[10px] text-hex-text-muted hover:text-hex-text border border-dashed border-hex-border rounded hover:border-hex-accent transition-colors"
|
|
476
|
+
>
|
|
477
|
+
<Plus size={10} />
|
|
478
|
+
Add config
|
|
479
|
+
</button>
|
|
480
|
+
)
|
|
481
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react'
|
|
2
|
+
import Editor, { type OnMount } from '@monaco-editor/react'
|
|
3
|
+
import type * as Monaco from 'monaco-editor'
|
|
4
|
+
|
|
5
|
+
interface PythonEditorProps {
|
|
6
|
+
value: string
|
|
7
|
+
onChange: (value: string) => void
|
|
8
|
+
height?: string | number
|
|
9
|
+
readOnly?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function PythonEditor({
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
height = '200px',
|
|
16
|
+
readOnly = false,
|
|
17
|
+
}: PythonEditorProps) {
|
|
18
|
+
const editorRef = useRef<Monaco.editor.IStandaloneCodeEditor | null>(null)
|
|
19
|
+
const monacoRef = useRef<typeof Monaco | null>(null)
|
|
20
|
+
|
|
21
|
+
const handleEditorMount: OnMount = useCallback((editor, monaco) => {
|
|
22
|
+
editorRef.current = editor
|
|
23
|
+
monacoRef.current = monaco
|
|
24
|
+
|
|
25
|
+
// Define hexdag dark theme for Python
|
|
26
|
+
monaco.editor.defineTheme('hexdag-python-dark', {
|
|
27
|
+
base: 'vs-dark',
|
|
28
|
+
inherit: true,
|
|
29
|
+
rules: [
|
|
30
|
+
{ token: 'comment', foreground: '6A9955', fontStyle: 'italic' },
|
|
31
|
+
{ token: 'keyword', foreground: 'C586C0' },
|
|
32
|
+
{ token: 'string', foreground: 'CE9178' },
|
|
33
|
+
{ token: 'number', foreground: 'B5CEA8' },
|
|
34
|
+
{ token: 'type', foreground: '4EC9B0' },
|
|
35
|
+
{ token: 'function', foreground: 'DCDCAA' },
|
|
36
|
+
{ token: 'variable', foreground: '9CDCFE' },
|
|
37
|
+
{ token: 'operator', foreground: 'D4D4D4' },
|
|
38
|
+
{ token: 'delimiter', foreground: 'D4D4D4' },
|
|
39
|
+
{ token: 'decorator', foreground: 'D7BA7D' },
|
|
40
|
+
],
|
|
41
|
+
colors: {
|
|
42
|
+
'editor.background': '#0f0f1a',
|
|
43
|
+
'editor.foreground': '#D4D4D4',
|
|
44
|
+
'editor.lineHighlightBackground': '#1a1a2e',
|
|
45
|
+
'editor.selectionBackground': '#264f78',
|
|
46
|
+
'editorCursor.foreground': '#8B5CF6',
|
|
47
|
+
'editorLineNumber.foreground': '#4a4a6a',
|
|
48
|
+
'editorLineNumber.activeForeground': '#8B5CF6',
|
|
49
|
+
'editor.inactiveSelectionBackground': '#1a1a2e',
|
|
50
|
+
'editorIndentGuide.background': '#2d2d4a',
|
|
51
|
+
'editorIndentGuide.activeBackground': '#4a4a6a',
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
monaco.editor.setTheme('hexdag-python-dark')
|
|
56
|
+
|
|
57
|
+
// Add Python snippets
|
|
58
|
+
monaco.languages.registerCompletionItemProvider('python', {
|
|
59
|
+
provideCompletionItems: (model: Monaco.editor.ITextModel, position: Monaco.Position) => {
|
|
60
|
+
const word = model.getWordUntilPosition(position)
|
|
61
|
+
const range = {
|
|
62
|
+
startLineNumber: position.lineNumber,
|
|
63
|
+
endLineNumber: position.lineNumber,
|
|
64
|
+
startColumn: word.startColumn,
|
|
65
|
+
endColumn: word.endColumn,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const suggestions: Monaco.languages.CompletionItem[] = [
|
|
69
|
+
{
|
|
70
|
+
label: 'def',
|
|
71
|
+
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
72
|
+
insertText: 'def ${1:function_name}(${2:args}):\n\t${3:pass}',
|
|
73
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
74
|
+
documentation: 'Define a function',
|
|
75
|
+
range,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
label: 'async def',
|
|
79
|
+
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
80
|
+
insertText: 'async def ${1:function_name}(${2:args}):\n\t${3:pass}',
|
|
81
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
82
|
+
documentation: 'Define an async function',
|
|
83
|
+
range,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
label: 'lambda',
|
|
87
|
+
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
88
|
+
insertText: 'lambda ${1:x}: ${2:x}',
|
|
89
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
90
|
+
documentation: 'Lambda expression',
|
|
91
|
+
range,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
label: 'class',
|
|
95
|
+
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
96
|
+
insertText: 'class ${1:ClassName}:\n\tdef __init__(self${2:, args}):\n\t\t${3:pass}',
|
|
97
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
98
|
+
documentation: 'Define a class',
|
|
99
|
+
range,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
label: 'for',
|
|
103
|
+
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
104
|
+
insertText: 'for ${1:item} in ${2:items}:\n\t${3:pass}',
|
|
105
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
106
|
+
documentation: 'For loop',
|
|
107
|
+
range,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
label: 'if',
|
|
111
|
+
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
112
|
+
insertText: 'if ${1:condition}:\n\t${2:pass}',
|
|
113
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
114
|
+
documentation: 'If statement',
|
|
115
|
+
range,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
label: 'try',
|
|
119
|
+
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
120
|
+
insertText: 'try:\n\t${1:pass}\nexcept ${2:Exception} as e:\n\t${3:raise}',
|
|
121
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
122
|
+
documentation: 'Try/except block',
|
|
123
|
+
range,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
label: 'with',
|
|
127
|
+
kind: monaco.languages.CompletionItemKind.Snippet,
|
|
128
|
+
insertText: 'with ${1:context} as ${2:var}:\n\t${3:pass}',
|
|
129
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
130
|
+
documentation: 'With statement',
|
|
131
|
+
range,
|
|
132
|
+
},
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
return { suggestions }
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
}, [])
|
|
139
|
+
|
|
140
|
+
const handleChange = useCallback(
|
|
141
|
+
(newValue: string | undefined) => {
|
|
142
|
+
if (newValue !== undefined) {
|
|
143
|
+
onChange(newValue)
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[onChange]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div className="w-full border border-hex-border rounded overflow-hidden">
|
|
151
|
+
<Editor
|
|
152
|
+
height={height}
|
|
153
|
+
defaultLanguage="python"
|
|
154
|
+
value={value}
|
|
155
|
+
onChange={handleChange}
|
|
156
|
+
onMount={handleEditorMount}
|
|
157
|
+
options={{
|
|
158
|
+
minimap: { enabled: false },
|
|
159
|
+
fontSize: 12,
|
|
160
|
+
fontFamily: '"JetBrains Mono", "Fira Code", monospace',
|
|
161
|
+
lineNumbers: 'on',
|
|
162
|
+
scrollBeyondLastLine: false,
|
|
163
|
+
automaticLayout: true,
|
|
164
|
+
tabSize: 4,
|
|
165
|
+
insertSpaces: true,
|
|
166
|
+
wordWrap: 'on',
|
|
167
|
+
readOnly,
|
|
168
|
+
scrollbar: {
|
|
169
|
+
vertical: 'auto',
|
|
170
|
+
horizontal: 'auto',
|
|
171
|
+
verticalScrollbarSize: 8,
|
|
172
|
+
horizontalScrollbarSize: 8,
|
|
173
|
+
},
|
|
174
|
+
padding: {
|
|
175
|
+
top: 8,
|
|
176
|
+
bottom: 8,
|
|
177
|
+
},
|
|
178
|
+
renderLineHighlight: 'line',
|
|
179
|
+
cursorBlinking: 'smooth',
|
|
180
|
+
cursorSmoothCaretAnimation: 'on',
|
|
181
|
+
smoothScrolling: true,
|
|
182
|
+
contextmenu: true,
|
|
183
|
+
bracketPairColorization: {
|
|
184
|
+
enabled: true,
|
|
185
|
+
},
|
|
186
|
+
guides: {
|
|
187
|
+
bracketPairs: true,
|
|
188
|
+
indentation: true,
|
|
189
|
+
},
|
|
190
|
+
}}
|
|
191
|
+
theme="hexdag-python-dark"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
)
|
|
195
|
+
}
|