qyro 2.0.0__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.
- qyro/__init__.py +17 -0
- qyro/adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/c/__init__.py +4 -0
- qyro/adapters/language_adapters/python/__init__.py +4 -0
- qyro/adapters/language_adapters/python/python_adapter.py +584 -0
- qyro/cli/__init__.py +8 -0
- qyro/cli/__main__.py +5 -0
- qyro/cli/cli.py +392 -0
- qyro/cli/interactive.py +297 -0
- qyro/common/__init__.py +37 -0
- qyro/common/animation.py +82 -0
- qyro/common/builder.py +434 -0
- qyro/common/compiler.py +895 -0
- qyro/common/config.py +93 -0
- qyro/common/constants.py +99 -0
- qyro/common/errors.py +176 -0
- qyro/common/frontend.py +74 -0
- qyro/common/health.py +358 -0
- qyro/common/kafka_manager.py +192 -0
- qyro/common/logging.py +149 -0
- qyro/common/memory.py +147 -0
- qyro/common/metrics.py +301 -0
- qyro/common/monitoring.py +468 -0
- qyro/common/parser.py +91 -0
- qyro/common/platform.py +609 -0
- qyro/common/redis_memory.py +1108 -0
- qyro/common/rpc.py +287 -0
- qyro/common/sandbox.py +191 -0
- qyro/common/schema_loader.py +33 -0
- qyro/common/secure_sandbox.py +490 -0
- qyro/common/toolchain_validator.py +617 -0
- qyro/common/type_generator.py +176 -0
- qyro/common/validation.py +401 -0
- qyro/common/validator.py +204 -0
- qyro/gateway/__init__.py +8 -0
- qyro/gateway/gateway.py +303 -0
- qyro/orchestrator/__init__.py +8 -0
- qyro/orchestrator/orchestrator.py +1223 -0
- qyro-2.0.0.dist-info/METADATA +244 -0
- qyro-2.0.0.dist-info/RECORD +45 -0
- qyro-2.0.0.dist-info/WHEEL +5 -0
- qyro-2.0.0.dist-info/entry_points.txt +2 -0
- qyro-2.0.0.dist-info/licenses/LICENSE +21 -0
- qyro-2.0.0.dist-info/top_level.txt +1 -0
qyro/common/config.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Qyro Configuration Module
|
|
3
|
+
Handles configuration management for the Qyro runtime.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class QyroConfig:
|
|
13
|
+
"""Configuration for Qyro runtime."""
|
|
14
|
+
# Redis configuration
|
|
15
|
+
redis_host: str = "localhost"
|
|
16
|
+
redis_port: int = 6379
|
|
17
|
+
redis_db: int = 0
|
|
18
|
+
redis_password: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
# Kafka configuration
|
|
21
|
+
kafka_bootstrap_servers: str = "localhost:9092"
|
|
22
|
+
kafka_topic_prefix: str = "Qyro_"
|
|
23
|
+
|
|
24
|
+
# Service configuration
|
|
25
|
+
service_host: str = "0.0.0.0"
|
|
26
|
+
service_port: int = 8765
|
|
27
|
+
|
|
28
|
+
# Debugging
|
|
29
|
+
debug: bool = False
|
|
30
|
+
|
|
31
|
+
# Resource limits
|
|
32
|
+
max_memory_mb: int = 512
|
|
33
|
+
max_cpu_percent: int = 80
|
|
34
|
+
|
|
35
|
+
# Process management
|
|
36
|
+
max_restarts: int = 5
|
|
37
|
+
restart_backoff: float = 2.0
|
|
38
|
+
|
|
39
|
+
def __post_init__(self):
|
|
40
|
+
"""Load configuration from environment variables if not provided."""
|
|
41
|
+
# Redis configuration
|
|
42
|
+
if self.redis_host == "localhost":
|
|
43
|
+
self.redis_host = os.getenv("REDIS_HOST", "localhost")
|
|
44
|
+
if self.redis_port == 6379:
|
|
45
|
+
self.redis_port = int(os.getenv("REDIS_PORT", "6379"))
|
|
46
|
+
if self.redis_password is None:
|
|
47
|
+
self.redis_password = os.getenv("REDIS_PASSWORD")
|
|
48
|
+
|
|
49
|
+
# Kafka configuration
|
|
50
|
+
if self.kafka_bootstrap_servers == "localhost:9092":
|
|
51
|
+
self.kafka_bootstrap_servers = os.getenv("KAFKA_BOOTSTRAP_SERVERS", "localhost:9092")
|
|
52
|
+
if self.kafka_topic_prefix == "Qyro_":
|
|
53
|
+
self.kafka_topic_prefix = os.getenv("KAFKA_TOPIC_PREFIX", "Qyro_")
|
|
54
|
+
|
|
55
|
+
# Service configuration
|
|
56
|
+
if self.service_host == "0.0.0.0":
|
|
57
|
+
self.service_host = os.getenv("SERVICE_HOST", "0.0.0.0")
|
|
58
|
+
if self.service_port == 8765:
|
|
59
|
+
self.service_port = int(os.getenv("SERVICE_PORT", "8765"))
|
|
60
|
+
|
|
61
|
+
# Debug configuration
|
|
62
|
+
if not self.debug:
|
|
63
|
+
self.debug = os.getenv("DEBUG", "").lower() == "true"
|
|
64
|
+
|
|
65
|
+
# Resource limits
|
|
66
|
+
if self.max_memory_mb == 512:
|
|
67
|
+
self.max_memory_mb = int(os.getenv("MAX_MEMORY_MB", "512"))
|
|
68
|
+
if self.max_cpu_percent == 80:
|
|
69
|
+
self.max_cpu_percent = int(os.getenv("MAX_CPU_PERCENT", "80"))
|
|
70
|
+
|
|
71
|
+
# Process management
|
|
72
|
+
if self.max_restarts == 5:
|
|
73
|
+
self.max_restarts = int(os.getenv("MAX_RESTARTS", "5"))
|
|
74
|
+
if self.restart_backoff == 2.0:
|
|
75
|
+
self.restart_backoff = float(os.getenv("RESTART_BACKOFF", "2.0"))
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def redis_url(self) -> str:
|
|
79
|
+
"""Get Redis URL for connection."""
|
|
80
|
+
if self.redis_password:
|
|
81
|
+
return f"redis://:{self.redis_password}@{self.redis_host}:{self.redis_port}/{self.redis_db}"
|
|
82
|
+
return f"redis://{self.redis_host}:{self.redis_port}/{self.redis_db}"
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def kafka_topics(self) -> dict:
|
|
86
|
+
"""Get Kafka topic names."""
|
|
87
|
+
return {
|
|
88
|
+
"state_change": f"{self.kafka_topic_prefix}state_change",
|
|
89
|
+
"module_events": f"{self.kafka_topic_prefix}module_events",
|
|
90
|
+
"rpc_requests": f"{self.kafka_topic_prefix}rpc_requests",
|
|
91
|
+
"rpc_responses": f"{self.kafka_topic_prefix}rpc_responses",
|
|
92
|
+
"broadcast": f"{self.kafka_topic_prefix}broadcast"
|
|
93
|
+
}
|
qyro/common/constants.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nexus Core Constants
|
|
3
|
+
Global constants shared across all language adapters for consistency.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Version
|
|
7
|
+
NEXUS_VERSION = "2.0.0"
|
|
8
|
+
PROTOCOL_VERSION = 3
|
|
9
|
+
|
|
10
|
+
# File Names - MUST match across all adapters
|
|
11
|
+
MEM_FILE = "nexus.mem"
|
|
12
|
+
LOCK_FILE = ".nexus_global.lock" # UNIFIED lock file for ALL adapters
|
|
13
|
+
|
|
14
|
+
# Memory Layout - NBP v3 (Nexus Binary Protocol)
|
|
15
|
+
# All offsets in bytes, all integers are little-endian
|
|
16
|
+
HEADER_SIZE = 32
|
|
17
|
+
|
|
18
|
+
# Header offsets
|
|
19
|
+
OFFSET_MAGIC = 0 # [0-4]: Magic bytes "NEXS"
|
|
20
|
+
OFFSET_VERSION = 4 # [4-8]: Protocol version (3)
|
|
21
|
+
OFFSET_SEQUENCE = 8 # [8-12]: Sequence number for optimistic concurrency
|
|
22
|
+
OFFSET_CHECKSUM = 12 # [12-16]: CRC32 checksum of data
|
|
23
|
+
OFFSET_REGISTRY = 16 # [16-20]: Registry offset (function registry start)
|
|
24
|
+
OFFSET_LENGTH = 20 # [20-24]: Data length
|
|
25
|
+
OFFSET_FLAGS = 24 # [24-28]: Flags (encrypted, compressed, etc.)
|
|
26
|
+
OFFSET_RESERVED = 28 # [28-32]: Reserved for future use
|
|
27
|
+
OFFSET_DATA = 32 # [32...]: JSON/binary data
|
|
28
|
+
|
|
29
|
+
# Magic bytes
|
|
30
|
+
MAGIC_BYTES = b"NEXS"
|
|
31
|
+
|
|
32
|
+
# Default sizes
|
|
33
|
+
DEFAULT_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB
|
|
34
|
+
SLOT_SIZE = 1024 # 1 KB per slot
|
|
35
|
+
MAX_SLOTS = 1000 # Maximum number of slots
|
|
36
|
+
|
|
37
|
+
# Function Calling - Reserved memory regions
|
|
38
|
+
REGISTRY_OFFSET = 1024 # Function registry starts at 1KB
|
|
39
|
+
REGISTRY_SIZE = 1024 # 1KB for registry
|
|
40
|
+
CALL_QUEUE_OFFSET = 2048 # Call queue starts at 2KB
|
|
41
|
+
CALL_QUEUE_SIZE = 2048 # 2KB for call queue
|
|
42
|
+
RESULT_QUEUE_OFFSET = 4096 # Result queue starts at 4KB
|
|
43
|
+
RESULT_QUEUE_SIZE = 4096 # 4KB for result queue
|
|
44
|
+
DATA_OFFSET = 8192 # User data starts at 8KB
|
|
45
|
+
|
|
46
|
+
# Timeouts (in seconds)
|
|
47
|
+
DEFAULT_LOCK_TIMEOUT = 5.0
|
|
48
|
+
DEFAULT_CALL_TIMEOUT = 30.0
|
|
49
|
+
DEFAULT_POLL_INTERVAL = 0.05 # 50ms for polling mode
|
|
50
|
+
|
|
51
|
+
# Flags (bitmask)
|
|
52
|
+
FLAG_ENCRYPTED = 0x01
|
|
53
|
+
FLAG_COMPRESSED = 0x02
|
|
54
|
+
FLAG_BINARY = 0x04 # MessagePack instead of JSON
|
|
55
|
+
|
|
56
|
+
# Error codes - must match across all languages
|
|
57
|
+
class ErrorCode:
|
|
58
|
+
# Memory errors (1xxx)
|
|
59
|
+
MEMORY_NOT_FOUND = 1001
|
|
60
|
+
MEMORY_LOCK_TIMEOUT = 1002
|
|
61
|
+
MEMORY_CORRUPTION = 1003
|
|
62
|
+
MEMORY_FULL = 1004
|
|
63
|
+
SLOT_OVERFLOW = 1005
|
|
64
|
+
CHECKSUM_MISMATCH = 1006
|
|
65
|
+
VERSION_MISMATCH = 1007
|
|
66
|
+
|
|
67
|
+
# JSON errors (2xxx)
|
|
68
|
+
JSON_PARSE_ERROR = 2001
|
|
69
|
+
JSON_ENCODE_ERROR = 2002
|
|
70
|
+
JSON_SCHEMA_MISMATCH = 2003
|
|
71
|
+
|
|
72
|
+
# Function call errors (3xxx)
|
|
73
|
+
FUNCTION_NOT_FOUND = 3001
|
|
74
|
+
FUNCTION_TIMEOUT = 3002
|
|
75
|
+
FUNCTION_ERROR = 3003
|
|
76
|
+
INVALID_ARGUMENTS = 3004
|
|
77
|
+
PERMISSION_DENIED = 3005
|
|
78
|
+
|
|
79
|
+
# Auth errors (4xxx)
|
|
80
|
+
AUTH_REQUIRED = 4001
|
|
81
|
+
TOKEN_EXPIRED = 4002
|
|
82
|
+
TOKEN_INVALID = 4003
|
|
83
|
+
INSUFFICIENT_PERMISSIONS = 4004
|
|
84
|
+
|
|
85
|
+
# Security errors (8xxx)
|
|
86
|
+
SECURITY_VIOLATION = 8001
|
|
87
|
+
INPUT_VALIDATION_FAILED = 8002
|
|
88
|
+
RESOURCE_LIMIT_EXCEEDED = 8003
|
|
89
|
+
UNTRUSTED_CODE_REJECTED = 8004
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# CRC32 polynomial for checksum
|
|
93
|
+
CRC32_POLYNOMIAL = 0xEDB88320
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def calculate_crc32(data: bytes) -> int:
|
|
97
|
+
"""Calculate CRC32 checksum matching the algorithm used by all adapters."""
|
|
98
|
+
import zlib
|
|
99
|
+
return zlib.crc32(data) & 0xFFFFFFFF
|
qyro/common/errors.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Qyro Error Handling Framework
|
|
3
|
+
Standardized error codes and exception classes for cross-language consistency.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from enum import IntEnum
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ErrorCode(IntEnum):
|
|
11
|
+
"""Standardized error codes across all Qyro components."""
|
|
12
|
+
|
|
13
|
+
# Success
|
|
14
|
+
SUCCESS = 0
|
|
15
|
+
|
|
16
|
+
# Memory Errors (1xxx)
|
|
17
|
+
MEMORY_NOT_FOUND = 1001
|
|
18
|
+
MEMORY_LOCK_TIMEOUT = 1002
|
|
19
|
+
MEMORY_CORRUPTION = 1003
|
|
20
|
+
MEMORY_FULL = 1004
|
|
21
|
+
MEMORY_ACCESS_DENIED = 1005
|
|
22
|
+
|
|
23
|
+
# JSON/Schema Errors (2xxx)
|
|
24
|
+
JSON_PARSE_ERROR = 2001
|
|
25
|
+
JSON_SCHEMA_MISMATCH = 2002
|
|
26
|
+
JSON_ENCODE_ERROR = 2003
|
|
27
|
+
SCHEMA_NOT_FOUND = 2004
|
|
28
|
+
SCHEMA_INVALID = 2005
|
|
29
|
+
|
|
30
|
+
# Auth Errors (3xxx)
|
|
31
|
+
AUTH_FAILED = 3001
|
|
32
|
+
AUTH_EXPIRED = 3002
|
|
33
|
+
AUTH_INVALID_TOKEN = 3003
|
|
34
|
+
AUTH_PERMISSION_DENIED = 3004
|
|
35
|
+
|
|
36
|
+
# Slot Errors (4xxx)
|
|
37
|
+
SLOT_OVERFLOW = 4001
|
|
38
|
+
SLOT_NOT_FOUND = 4002
|
|
39
|
+
SLOT_LOCKED = 4003
|
|
40
|
+
|
|
41
|
+
# Process Errors (5xxx)
|
|
42
|
+
PROCESS_SPAWN_FAILED = 5001
|
|
43
|
+
PROCESS_CRASH = 5002
|
|
44
|
+
PROCESS_TIMEOUT = 5003
|
|
45
|
+
|
|
46
|
+
# Compile Errors (6xxx)
|
|
47
|
+
COMPILE_C_FAILED = 6001
|
|
48
|
+
COMPILE_RUST_FAILED = 6002
|
|
49
|
+
COMPILE_JAVA_FAILED = 6003
|
|
50
|
+
COMPILE_GO_FAILED = 6004
|
|
51
|
+
|
|
52
|
+
# Parse Errors (7xxx)
|
|
53
|
+
PARSE_FILE_NOT_FOUND = 7001
|
|
54
|
+
PARSE_INVALID_BLOCK = 7002
|
|
55
|
+
PARSE_IMPORT_FAILED = 7003
|
|
56
|
+
|
|
57
|
+
# Security Errors (8xxx)
|
|
58
|
+
SECURITY_VIOLATION = 8001
|
|
59
|
+
INPUT_VALIDATION_FAILED = 8002
|
|
60
|
+
RESOURCE_LIMIT_EXCEEDED = 8003
|
|
61
|
+
UNTRUSTED_CODE_REJECTED = 8004
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class QyroError(Exception):
|
|
65
|
+
"""Base exception for all Qyro errors."""
|
|
66
|
+
def __init__(self, code: ErrorCode, message: str, details: dict = None):
|
|
67
|
+
self.code = code
|
|
68
|
+
self.message = message
|
|
69
|
+
self.details = details or {}
|
|
70
|
+
super().__init__(f"[{code.value}] {message}")
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
73
|
+
return {
|
|
74
|
+
"error": True,
|
|
75
|
+
"code": self.code.value,
|
|
76
|
+
"message": self.message,
|
|
77
|
+
"details": self.details
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Specific exception classes for common error types
|
|
82
|
+
class MemoryError(QyroError):
|
|
83
|
+
def __init__(self, code: ErrorCode = ErrorCode.MEMORY_NOT_FOUND, message: str = "Memory access error", **details):
|
|
84
|
+
super().__init__(code, message, details)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class JSONError(QyroError):
|
|
88
|
+
def __init__(self, code: ErrorCode = ErrorCode.JSON_PARSE_ERROR, message: str = "JSON error", **details):
|
|
89
|
+
super().__init__(code, message, details)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class AuthError(QyroError):
|
|
93
|
+
def __init__(self, code: ErrorCode = ErrorCode.AUTH_FAILED, message: str = "Authentication error", **details):
|
|
94
|
+
super().__init__(code, message, details)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class CompileError(QyroError):
|
|
98
|
+
def __init__(self, code: ErrorCode = ErrorCode.COMPILE_C_FAILED, message: str = "Compilation error", **details):
|
|
99
|
+
super().__init__(code, message, details)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ParseError(QyroError):
|
|
103
|
+
def __init__(self, code: 'ErrorCode' = None, message: str = "Parse error", **details):
|
|
104
|
+
if code is None:
|
|
105
|
+
code = ErrorCode.PARSE_FILE_NOT_FOUND
|
|
106
|
+
super().__init__(code, message, details)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class SecurityError(QyroError):
|
|
110
|
+
def __init__(self, code: 'ErrorCode' = None, message: str = "Security violation", **details):
|
|
111
|
+
if code is None:
|
|
112
|
+
code = ErrorCode.SECURITY_VIOLATION
|
|
113
|
+
super().__init__(code, message, details)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ValidationError(QyroError):
|
|
117
|
+
def __init__(self, code: 'ErrorCode' = None, message: str = "Input validation failed", **details):
|
|
118
|
+
if code is None:
|
|
119
|
+
code = ErrorCode.INPUT_VALIDATION_FAILED
|
|
120
|
+
super().__init__(code, message, details)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ResourceLimitError(QyroError):
|
|
124
|
+
def __init__(self, code: 'ErrorCode' = None, message: str = "Resource limit exceeded", **details):
|
|
125
|
+
if code is None:
|
|
126
|
+
code = ErrorCode.RESOURCE_LIMIT_EXCEEDED
|
|
127
|
+
super().__init__(code, message, details)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Result type for operations that can fail
|
|
131
|
+
class Result:
|
|
132
|
+
"""Rust-style Result type for safe error handling."""
|
|
133
|
+
|
|
134
|
+
def __init__(self, value=None, error: QyroError = None):
|
|
135
|
+
self._value = value
|
|
136
|
+
self._error = error
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def ok(cls, value):
|
|
140
|
+
return cls(value=value)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def err(cls, error: QyroError):
|
|
144
|
+
return cls(error=error)
|
|
145
|
+
|
|
146
|
+
def is_ok(self) -> bool:
|
|
147
|
+
return self._error is None
|
|
148
|
+
|
|
149
|
+
def is_err(self) -> bool:
|
|
150
|
+
return self._error is not None
|
|
151
|
+
|
|
152
|
+
def unwrap(self):
|
|
153
|
+
if self._error:
|
|
154
|
+
raise self._error
|
|
155
|
+
return self._value
|
|
156
|
+
|
|
157
|
+
def unwrap_or(self, default):
|
|
158
|
+
return self._value if self._error is None else default
|
|
159
|
+
|
|
160
|
+
def map(self, fn):
|
|
161
|
+
if self._error:
|
|
162
|
+
return self
|
|
163
|
+
try:
|
|
164
|
+
return Result.ok(fn(self._value))
|
|
165
|
+
except QyroError as e:
|
|
166
|
+
return Result.err(e)
|
|
167
|
+
|
|
168
|
+
def __repr__(self):
|
|
169
|
+
if self._error:
|
|
170
|
+
return f"Err({self._error})"
|
|
171
|
+
return f"Ok({self._value})"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Qyro compatibility aliases
|
|
175
|
+
NexusError = QyroError
|
|
176
|
+
QyroErrorCode = ErrorCode
|
qyro/common/frontend.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nexus Frontend Compiler
|
|
3
|
+
Handles compilation and setup for frontend frameworks (React, Next.js).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
from typing import Dict, Any, List
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from .logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger("nexus.frontend")
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class FrontendConfig:
|
|
16
|
+
framework: str = "react" # 'react' or 'nextjs'
|
|
17
|
+
port: int = 3000
|
|
18
|
+
|
|
19
|
+
class FrontendCompiler:
|
|
20
|
+
def __init__(self, output_dir: str = "nexus_frontend"):
|
|
21
|
+
self.output_dir = output_dir
|
|
22
|
+
|
|
23
|
+
def compile_react(self, code: str, config: FrontendConfig) -> Dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
Compile React code block.
|
|
26
|
+
Since we're using Vite, "compilation" mainly means saving the file
|
|
27
|
+
to the correct location for Vite to pick up.
|
|
28
|
+
"""
|
|
29
|
+
# Ensure output directory exists (created by orchestrator, but just in case)
|
|
30
|
+
if not os.path.exists(self.output_dir):
|
|
31
|
+
os.makedirs(self.output_dir)
|
|
32
|
+
|
|
33
|
+
src_dir = os.path.join(self.output_dir, "src")
|
|
34
|
+
if not os.path.exists(src_dir):
|
|
35
|
+
os.makedirs(src_dir)
|
|
36
|
+
|
|
37
|
+
# Save the component code
|
|
38
|
+
# We assume the code block contains the full component definition
|
|
39
|
+
# typically matching what goes into NexusComponent.tsx
|
|
40
|
+
component_path = os.path.join(src_dir, "NexusComponent.tsx")
|
|
41
|
+
|
|
42
|
+
# Add necessary imports if missing (simple heuristic)
|
|
43
|
+
final_code = code
|
|
44
|
+
if "import React" not in code and "import { useState" not in code:
|
|
45
|
+
final_code = "import React, { useState, useEffect, useRef } from 'react';\n" + code
|
|
46
|
+
|
|
47
|
+
# If the code doesn't export default, we might need to wrap or fix it.
|
|
48
|
+
# But for now, we assume the user provides a valid component.
|
|
49
|
+
# If it's a raw functional component without export, we might append the export.
|
|
50
|
+
if "export default" not in final_code:
|
|
51
|
+
final_code += "\n\nexport default NexusComponent;"
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
with open(component_path, "w", encoding='utf-8') as f:
|
|
55
|
+
f.write(final_code)
|
|
56
|
+
|
|
57
|
+
logger.info(f"React component saved to {component_path}")
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
"type": "react",
|
|
61
|
+
"src": component_path,
|
|
62
|
+
"framework": "vite"
|
|
63
|
+
}
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Failed to save React component: {e}")
|
|
66
|
+
raise e
|
|
67
|
+
|
|
68
|
+
def compile_nextjs(self, code: str, config: FrontendConfig) -> Dict[str, Any]:
|
|
69
|
+
"""
|
|
70
|
+
Compile Next.js code block.
|
|
71
|
+
"""
|
|
72
|
+
# Similar logic for Next.js pages/components
|
|
73
|
+
# For simplicity, we might map this to pages/index.tsx
|
|
74
|
+
pass
|