feat(web): copy awel flow (#1200)

Co-authored-by: Fangyin Cheng <staneyffer@gmail.com>
This commit is contained in:
Hzh_97
2024-02-28 21:03:23 +08:00
committed by GitHub
parent 0837da48ba
commit 673ddaab5b
68 changed files with 898 additions and 190 deletions

View File

@@ -649,8 +649,8 @@ class BaseMetadata(BaseResource):
# TODO, skip the optional parameters.
raise FlowParameterMetadataException(
f"Parameters count not match(current key: {self.id}). "
f"Expected {len(self.parameters)}, "
f"but got {len(view_parameters)} from JSON metadata."
f"Expected {len(current_required_parameters)}, "
f"but got {len(view_required_parameters)} from JSON metadata."
f"Required parameters: {current_required_parameters.keys()}, "
f"but got {view_required_parameters.keys()}."
)

View File

@@ -26,6 +26,9 @@ from .exceptions import (
logger = logging.getLogger(__name__)
AWEL_FLOW_VERSION = "0.1.1"
class FlowPositionData(BaseModel):
"""Position of a node in a flow."""
@@ -152,12 +155,10 @@ class State(str, Enum):
INITIALIZING = "initializing"
DEVELOPING = "developing"
TESTING = "testing"
READY_TO_DEPLOY = "ready_to_deploy"
DEPLOYED = "deployed"
RUNNING = "running"
PAUSED = "paused"
DISABLED = "disabled"
ENABLED = "enabled"
LOAD_FAILED = "load_failed"
@classmethod
def value_of(cls, value: Optional[str]) -> "State":
@@ -169,6 +170,60 @@ class State(str, Enum):
return state
raise ValueError(f"Invalid state value: {value}")
@classmethod
def can_change_state(cls, current_state: "State", new_state: "State") -> bool:
"""Change the state of the flow panel."""
allowed_transitions: Dict[State, List[State]] = {
State.INITIALIZING: [
State.DEVELOPING,
State.INITIALIZING,
State.LOAD_FAILED,
],
State.DEVELOPING: [
State.TESTING,
State.DEPLOYED,
State.DISABLED,
State.DEVELOPING,
State.LOAD_FAILED,
],
State.TESTING: [
State.TESTING,
State.DEPLOYED,
State.DEVELOPING,
State.DISABLED,
State.RUNNING,
State.LOAD_FAILED,
],
State.DEPLOYED: [
State.DEPLOYED,
State.DEVELOPING,
State.TESTING,
State.DISABLED,
State.RUNNING,
State.LOAD_FAILED,
],
State.RUNNING: [
State.RUNNING,
State.DEPLOYED,
State.TESTING,
State.DISABLED,
],
State.DISABLED: [State.DISABLED, State.DEPLOYED],
State.LOAD_FAILED: [
State.LOAD_FAILED,
State.DEVELOPING,
State.DEPLOYED,
State.DISABLED,
],
}
if new_state in allowed_transitions[current_state]:
return True
else:
logger.error(
f"Invalid state transition from {current_state} to {new_state}"
)
return False
class FlowCategory(str, Enum):
"""Flow category."""
@@ -219,6 +274,11 @@ class FlowPanel(BaseModel):
state: State = Field(
default=State.INITIALIZING, description="Current state of the flow panel"
)
error_message: Optional[str] = Field(
None,
description="Error message of load the flow panel",
examples=["Unable to load the flow panel."],
)
source: Optional[str] = Field(
"DBGPT-WEB",
description="Source of the flow panel",
@@ -229,7 +289,9 @@ class FlowPanel(BaseModel):
description="Source url of the flow panel",
)
version: Optional[str] = Field(
"0.1.0", description="Version of the flow panel", examples=["0.1.0", "0.2.0"]
AWEL_FLOW_VERSION,
description="Version of the flow panel",
examples=["0.1.0", "0.2.0"],
)
editable: bool = Field(
True,
@@ -251,26 +313,6 @@ class FlowPanel(BaseModel):
examples=["2021-08-01 12:00:00", "2021-08-01 12:00:01", "2021-08-01 12:00:02"],
)
def change_state(self, new_state: State) -> bool:
"""Change the state of the flow panel."""
allowed_transitions: Dict[State, List[State]] = {
State.INITIALIZING: [State.DEVELOPING],
State.DEVELOPING: [State.TESTING, State.DISABLED],
State.TESTING: [State.READY_TO_DEPLOY, State.DEVELOPING, State.DISABLED],
State.READY_TO_DEPLOY: [State.DEPLOYED, State.DEVELOPING],
State.DEPLOYED: [State.RUNNING, State.DISABLED],
State.RUNNING: [State.PAUSED, State.DISABLED, State.DEPLOYED],
State.PAUSED: [State.RUNNING, State.DISABLED],
State.DISABLED: [State.ENABLED],
State.ENABLED: [s for s in State if s != State.INITIALIZING],
}
if new_state in allowed_transitions[self.state]:
self.state = new_state
return True
else:
logger.error(f"Invalid state transition from {self.state} to {new_state}")
return False
@root_validator(pre=True)
def pre_fill(cls, values: Dict[str, Any]) -> Dict[str, Any]:
"""Pre fill the metadata."""

View File

@@ -4,10 +4,12 @@ After DB-GPT started, the trigger manager will be initialized and register all t
"""
import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Dict, Optional
from collections import defaultdict
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union
from dbgpt.component import BaseComponent, ComponentType, SystemApp
from ..util.http_util import join_paths
from .base import Trigger
if TYPE_CHECKING:
@@ -64,6 +66,7 @@ class HttpTriggerManager(TriggerManager):
self._router_prefix = router_prefix
self._router = router
self._trigger_map: Dict[str, Trigger] = {}
self._router_tables: Dict[str, Set[str]] = defaultdict(set)
def register_trigger(self, trigger: Any, system_app: SystemApp) -> None:
"""Register a trigger to current manager."""
@@ -73,15 +76,23 @@ class HttpTriggerManager(TriggerManager):
raise ValueError(f"Current trigger {trigger} not an object of HttpTrigger")
trigger_id = trigger.node_id
if trigger_id not in self._trigger_map:
if trigger.register_to_app():
app = system_app.app
if not app:
raise ValueError("System app not initialized")
# Mount to app, support dynamic route.
trigger.mount_to_app(app, self._router_prefix)
else:
trigger.mount_to_router(self._router)
self._trigger_map[trigger_id] = trigger
path = join_paths(self._router_prefix, trigger._endpoint)
methods = trigger._methods
# Check whether the route is already registered
self._register_route_tables(path, methods)
try:
if trigger.register_to_app():
app = system_app.app
if not app:
raise ValueError("System app not initialized")
# Mount to app, support dynamic route.
trigger.mount_to_app(app, self._router_prefix)
else:
trigger.mount_to_router(self._router)
self._trigger_map[trigger_id] = trigger
except Exception as e:
self._unregister_route_tables(path, methods)
raise e
def unregister_trigger(self, trigger: Any, system_app: SystemApp) -> None:
"""Unregister a trigger to current manager."""
@@ -96,6 +107,9 @@ class HttpTriggerManager(TriggerManager):
if not app:
raise ValueError("System app not initialized")
trigger.remove_from_app(app, self._router_prefix)
self._unregister_route_tables(
join_paths(self._router_prefix, trigger._endpoint), trigger._methods
)
del self._trigger_map[trigger_id]
def _init_app(self, system_app: SystemApp):
@@ -120,6 +134,34 @@ class HttpTriggerManager(TriggerManager):
"""
return len(self._trigger_map) > 0
def _register_route_tables(
self, path: str, methods: Optional[Union[str, List[str]]]
):
methods = self._parse_methods(methods)
tables = self._router_tables[path]
for m in methods:
if m in tables:
raise ValueError(f"Route {path} method {m} already registered")
tables.add(m)
self._router_tables[path] = tables
def _unregister_route_tables(
self, path: str, methods: Optional[Union[str, List[str]]]
):
methods = self._parse_methods(methods)
tables = self._router_tables[path]
for m in methods:
if m in tables:
tables.remove(m)
self._router_tables[path] = tables
def _parse_methods(self, methods: Optional[Union[str, List[str]]]) -> List[str]:
if not methods:
return ["GET"]
elif isinstance(methods, str):
return [methods]
return [m.upper() for m in methods]
class DefaultTriggerManager(TriggerManager, BaseComponent):
"""Default trigger manager for AWEL.