flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__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.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
flock/webapp/app/main.py
DELETED
|
@@ -1,1070 +0,0 @@
|
|
|
1
|
-
# src/flock/webapp/app/main.py
|
|
2
|
-
import asyncio
|
|
3
|
-
import json
|
|
4
|
-
import os # Added import
|
|
5
|
-
import shutil
|
|
6
|
-
|
|
7
|
-
# Added for share link creation
|
|
8
|
-
import uuid
|
|
9
|
-
from contextlib import asynccontextmanager
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
import markdown2 # Import markdown2
|
|
14
|
-
from fastapi import (
|
|
15
|
-
Depends,
|
|
16
|
-
FastAPI,
|
|
17
|
-
File,
|
|
18
|
-
Form,
|
|
19
|
-
HTTPException,
|
|
20
|
-
Query,
|
|
21
|
-
Request,
|
|
22
|
-
UploadFile,
|
|
23
|
-
)
|
|
24
|
-
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
25
|
-
from fastapi.staticfiles import StaticFiles
|
|
26
|
-
from fastapi.templating import Jinja2Templates
|
|
27
|
-
from pydantic import BaseModel
|
|
28
|
-
|
|
29
|
-
from flock.core.api.endpoints import create_api_router
|
|
30
|
-
from flock.core.api.run_store import RunStore
|
|
31
|
-
|
|
32
|
-
# Import core Flock components and API related modules
|
|
33
|
-
from flock.core.flock import Flock # For type hinting
|
|
34
|
-
from flock.core.flock_scheduler import FlockScheduler
|
|
35
|
-
from flock.core.logging.logging import get_logger # For logging
|
|
36
|
-
from flock.core.util.splitter import parse_schema
|
|
37
|
-
|
|
38
|
-
# Import UI-specific routers
|
|
39
|
-
from flock.webapp.app.api import (
|
|
40
|
-
agent_management,
|
|
41
|
-
execution,
|
|
42
|
-
flock_management,
|
|
43
|
-
registry_viewer,
|
|
44
|
-
)
|
|
45
|
-
from flock.webapp.app.config import (
|
|
46
|
-
DEFAULT_THEME_NAME,
|
|
47
|
-
FLOCK_FILES_DIR,
|
|
48
|
-
THEMES_DIR,
|
|
49
|
-
get_current_theme_name,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
# Import dependency management and config
|
|
53
|
-
from flock.webapp.app.dependencies import (
|
|
54
|
-
get_pending_custom_endpoints_and_clear,
|
|
55
|
-
get_shared_link_store,
|
|
56
|
-
set_global_flock_services,
|
|
57
|
-
set_global_shared_link_store,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
# Import service functions (which now expect app_state)
|
|
61
|
-
from flock.webapp.app.middleware import ProxyHeadersMiddleware
|
|
62
|
-
from flock.webapp.app.services.flock_service import (
|
|
63
|
-
clear_current_flock_service,
|
|
64
|
-
create_new_flock_service,
|
|
65
|
-
get_available_flock_files,
|
|
66
|
-
get_flock_preview_service,
|
|
67
|
-
load_flock_from_file_service,
|
|
68
|
-
# Note: get_current_flock_instance/filename are removed from service,
|
|
69
|
-
# as main.py will use request.app.state for this.
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
# Added for share link creation
|
|
73
|
-
from flock.webapp.app.services.sharing_models import SharedLinkConfig
|
|
74
|
-
from flock.webapp.app.services.sharing_store import (
|
|
75
|
-
SharedLinkStoreInterface,
|
|
76
|
-
create_shared_link_store,
|
|
77
|
-
)
|
|
78
|
-
from flock.webapp.app.theme_mapper import alacritty_to_pico
|
|
79
|
-
|
|
80
|
-
logger = get_logger("webapp.main")
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
try:
|
|
84
|
-
from flock.core.logging.formatters.themed_formatter import (
|
|
85
|
-
load_theme_from_file,
|
|
86
|
-
)
|
|
87
|
-
THEME_LOADER_AVAILABLE = True
|
|
88
|
-
except ImportError:
|
|
89
|
-
logger.warning("Could not import flock.core theme loading utilities.")
|
|
90
|
-
THEME_LOADER_AVAILABLE = False
|
|
91
|
-
|
|
92
|
-
# --- .env helpers (copied from original main.py for self-containment) ---
|
|
93
|
-
ENV_FILE_PATH = Path(".env") #Path(os.getenv("FLOCK_WEB_ENV_FILE", Path.home() / ".flock" / ".env"))
|
|
94
|
-
#ENV_FILE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
95
|
-
SHOW_SECRETS_KEY = "SHOW_SECRETS"
|
|
96
|
-
|
|
97
|
-
def load_env_file_web() -> dict[str, str]:
|
|
98
|
-
env_vars: dict[str, str] = {}
|
|
99
|
-
if not ENV_FILE_PATH.exists(): return env_vars
|
|
100
|
-
with open(ENV_FILE_PATH) as f: lines = f.readlines()
|
|
101
|
-
for line in lines:
|
|
102
|
-
line = line.strip()
|
|
103
|
-
if not line: env_vars[""] = ""; continue
|
|
104
|
-
if line.startswith("#"): env_vars[line] = ""; continue
|
|
105
|
-
if "=" in line: k, v = line.split("=", 1); env_vars[k] = v
|
|
106
|
-
else: env_vars[line] = ""
|
|
107
|
-
return env_vars
|
|
108
|
-
|
|
109
|
-
def save_env_file_web(env_vars: dict[str, str]):
|
|
110
|
-
try:
|
|
111
|
-
with open(ENV_FILE_PATH, "w") as f:
|
|
112
|
-
for k, v in env_vars.items():
|
|
113
|
-
if k.startswith("#"): f.write(f"{k}\n")
|
|
114
|
-
elif not k: f.write("\n")
|
|
115
|
-
else: f.write(f"{k}={v}\n")
|
|
116
|
-
except Exception as e: logger.error(f"[Settings] Failed to save .env: {e}")
|
|
117
|
-
|
|
118
|
-
def is_sensitive_web(key: str) -> bool:
|
|
119
|
-
patterns = ["key", "token", "secret", "password", "api", "pat"]; low = key.lower()
|
|
120
|
-
return any(p in low for p in patterns)
|
|
121
|
-
|
|
122
|
-
def mask_sensitive_value_web(value: str) -> str:
|
|
123
|
-
if not value: return value
|
|
124
|
-
if len(value) <= 4: return "••••"
|
|
125
|
-
return value[:2] + "•" * (len(value) - 4) + value[-2:]
|
|
126
|
-
|
|
127
|
-
def create_hx_trigger_header(triggers: dict[str, Any]) -> str:
|
|
128
|
-
"""Helper function to create HX-Trigger header with JSON serialization."""
|
|
129
|
-
return json.dumps(triggers)
|
|
130
|
-
|
|
131
|
-
def get_show_secrets_setting_web(env_vars: dict[str, str]) -> bool:
|
|
132
|
-
return env_vars.get(SHOW_SECRETS_KEY, "false").lower() == "true"
|
|
133
|
-
|
|
134
|
-
def set_show_secrets_setting_web(show: bool):
|
|
135
|
-
env_vars = load_env_file_web()
|
|
136
|
-
env_vars[SHOW_SECRETS_KEY] = str(show)
|
|
137
|
-
save_env_file_web(env_vars)
|
|
138
|
-
# --- End .env helpers ---
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
@asynccontextmanager
|
|
142
|
-
async def lifespan(app: FastAPI):
|
|
143
|
-
logger.info("FastAPI application starting up...")
|
|
144
|
-
# Flock instance and RunStore are expected to be set on app.state
|
|
145
|
-
# by `start_unified_server` in `webapp/run.py` *before* uvicorn starts the app.
|
|
146
|
-
# The call to `set_global_flock_services` also happens there. # Initialize and set the SharedLinkStore
|
|
147
|
-
try:
|
|
148
|
-
logger.info("Initializing SharedLinkStore using factory...")
|
|
149
|
-
shared_link_store = create_shared_link_store()
|
|
150
|
-
await shared_link_store.initialize() # Create tables if they don't exist
|
|
151
|
-
set_global_shared_link_store(shared_link_store)
|
|
152
|
-
logger.info("SharedLinkStore initialized and set globally.")
|
|
153
|
-
except Exception as e:
|
|
154
|
-
logger.error(f"Failed to initialize SharedLinkStore: {e}", exc_info=True)# Configure chat features with clear precedence:
|
|
155
|
-
# 1. Value set by start_unified_server (programmatic)
|
|
156
|
-
# 2. Environment variables (standalone mode)
|
|
157
|
-
programmatic_chat_enabled = getattr(app.state, "chat_enabled", None)
|
|
158
|
-
env_start_mode = os.environ.get("FLOCK_START_MODE")
|
|
159
|
-
env_chat_enabled = os.environ.get("FLOCK_CHAT_ENABLED", "false").lower() == "true"
|
|
160
|
-
|
|
161
|
-
if programmatic_chat_enabled is not None:
|
|
162
|
-
# Programmatic setting takes precedence (from start_unified_server)
|
|
163
|
-
should_enable_chat_routes = programmatic_chat_enabled
|
|
164
|
-
logger.info(f"Using programmatic chat_enabled setting: {should_enable_chat_routes}")
|
|
165
|
-
elif env_start_mode == "chat":
|
|
166
|
-
should_enable_chat_routes = True
|
|
167
|
-
app.state.initial_redirect_to_chat = True
|
|
168
|
-
app.state.chat_enabled = True
|
|
169
|
-
logger.info("FLOCK_START_MODE='chat'. Enabling chat routes and setting redirect.")
|
|
170
|
-
elif env_chat_enabled:
|
|
171
|
-
should_enable_chat_routes = True
|
|
172
|
-
app.state.chat_enabled = True
|
|
173
|
-
logger.info("FLOCK_CHAT_ENABLED='true'. Enabling chat routes.")
|
|
174
|
-
else:
|
|
175
|
-
should_enable_chat_routes = False
|
|
176
|
-
app.state.chat_enabled = False
|
|
177
|
-
logger.info("Chat routes disabled (no programmatic or environment setting).")
|
|
178
|
-
|
|
179
|
-
if should_enable_chat_routes:
|
|
180
|
-
try:
|
|
181
|
-
from flock.webapp.app.chat import router as chat_router
|
|
182
|
-
app.include_router(chat_router, tags=["Chat"])
|
|
183
|
-
logger.info("Chat routes included in the application.")
|
|
184
|
-
except Exception as e:
|
|
185
|
-
logger.error(f"Failed to include chat routes during lifespan startup: {e}", exc_info=True) # If in standalone chat mode, strip non-essential UI routes
|
|
186
|
-
if env_start_mode == "chat":
|
|
187
|
-
from fastapi.routing import APIRoute
|
|
188
|
-
logger.info("FLOCK_START_MODE='chat'. Stripping non-chat UI routes.")
|
|
189
|
-
|
|
190
|
-
# Define tags for routes to KEEP.
|
|
191
|
-
# "Chat" for primary chat functionality.
|
|
192
|
-
# "Chat Sharing" for shared chat links & pages.
|
|
193
|
-
# API tags might be needed if chat agents make internal API calls or for general health/docs.
|
|
194
|
-
# Public static files (/static/...) are typically handled by app.mount and not in app.router.routes directly this way.
|
|
195
|
-
allowed_tags_for_chat_mode = {
|
|
196
|
-
"Chat",
|
|
197
|
-
"Chat Sharing",
|
|
198
|
-
"Flock API Core", # Keep core API for potential underlying needs
|
|
199
|
-
"Flock API Custom Endpoints" # Keep custom API endpoints
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
def _route_is_allowed_in_chat_mode(route: APIRoute) -> bool:
|
|
203
|
-
# Keep documentation (e.g. /docs, /openapi.json - usually no tags or specific tags)
|
|
204
|
-
# and non-API utility routes (often no tags).
|
|
205
|
-
if not hasattr(route, "tags") or not route.tags:
|
|
206
|
-
# Check common doc paths explicitly as they might not have tags or might have default tags
|
|
207
|
-
if route.path in ["/docs", "/openapi.json", "/redoc"]:
|
|
208
|
-
return True
|
|
209
|
-
# Allow other untagged routes for now, assuming they are essential (e.g. static mounts if they appeared here)
|
|
210
|
-
# This might need refinement if untagged UI routes exist.
|
|
211
|
-
return True
|
|
212
|
-
return any(tag in allowed_tags_for_chat_mode for tag in route.tags)
|
|
213
|
-
|
|
214
|
-
original_route_count = len(app.router.routes)
|
|
215
|
-
app.router.routes = [r for r in app.router.routes if _route_is_allowed_in_chat_mode(r)]
|
|
216
|
-
num_removed = original_route_count - len(app.router.routes)
|
|
217
|
-
logger.info(f"Stripped {num_removed} routes for chat-only mode. {len(app.router.routes)} routes remaining.")
|
|
218
|
-
|
|
219
|
-
if num_removed > 0 and hasattr(app, "openapi_schema"):
|
|
220
|
-
app.openapi_schema = None # Clear cached OpenAPI schema to regenerate
|
|
221
|
-
logger.info("Cleared OpenAPI schema cache due to route removal.")
|
|
222
|
-
|
|
223
|
-
# Add custom routes if any were passed during server startup
|
|
224
|
-
# These are retrieved from the dependency module where `start_unified_server` stored them.
|
|
225
|
-
pending_endpoints = get_pending_custom_endpoints_and_clear()
|
|
226
|
-
if pending_endpoints:
|
|
227
|
-
flock_instance_from_state: Flock | None = getattr(app.state, "flock_instance", None)
|
|
228
|
-
if flock_instance_from_state:
|
|
229
|
-
from flock.core.api.main import (
|
|
230
|
-
FlockAPI, # Local import for this specific task
|
|
231
|
-
)
|
|
232
|
-
# Create a temporary FlockAPI service object just for adding routes
|
|
233
|
-
temp_flock_api_service = FlockAPI(
|
|
234
|
-
flock_instance_from_state,
|
|
235
|
-
custom_endpoints=pending_endpoints
|
|
236
|
-
)
|
|
237
|
-
temp_flock_api_service.add_custom_routes_to_app(app)
|
|
238
|
-
logger.info(f"Lifespan: Added {len(pending_endpoints)} custom API routes to main app.")
|
|
239
|
-
else:
|
|
240
|
-
logger.warning("Lifespan: Pending custom endpoints found, but no Flock instance in app.state. Cannot add custom routes.")
|
|
241
|
-
|
|
242
|
-
# --- Add Scheduler Startup Logic ---
|
|
243
|
-
flock_instance_from_state: Flock | None = getattr(app.state, "flock_instance", None)
|
|
244
|
-
if flock_instance_from_state:
|
|
245
|
-
# Create and start the scheduler
|
|
246
|
-
scheduler = FlockScheduler(flock_instance_from_state)
|
|
247
|
-
app.state.flock_scheduler = scheduler # Store for access during shutdown
|
|
248
|
-
|
|
249
|
-
scheduler_loop_task = await scheduler.start() # Start returns the task
|
|
250
|
-
if scheduler_loop_task:
|
|
251
|
-
app.state.flock_scheduler_task = scheduler_loop_task # Store the task
|
|
252
|
-
logger.info("FlockScheduler background task started.")
|
|
253
|
-
else:
|
|
254
|
-
app.state.flock_scheduler_task = None
|
|
255
|
-
logger.info("FlockScheduler initialized, but no scheduled agents found or loop not started.")
|
|
256
|
-
else:
|
|
257
|
-
app.state.flock_scheduler = None
|
|
258
|
-
app.state.flock_scheduler_task = None
|
|
259
|
-
logger.warning("No Flock instance found in app.state; FlockScheduler not started.")
|
|
260
|
-
# --- End Scheduler Startup Logic ---
|
|
261
|
-
|
|
262
|
-
yield
|
|
263
|
-
logger.info("FastAPI application shutting down...")
|
|
264
|
-
|
|
265
|
-
# --- Add Scheduler Shutdown Logic ---
|
|
266
|
-
logger.info("FastAPI application initiating shutdown...")
|
|
267
|
-
scheduler_to_stop: FlockScheduler | None = getattr(app.state, "flock_scheduler", None)
|
|
268
|
-
scheduler_task_to_await: asyncio.Task | None = getattr(app.state, "flock_scheduler_task", None)
|
|
269
|
-
|
|
270
|
-
if scheduler_to_stop:
|
|
271
|
-
logger.info("Attempting to stop FlockScheduler...")
|
|
272
|
-
await scheduler_to_stop.stop() # Signal the scheduler loop to stop
|
|
273
|
-
|
|
274
|
-
if scheduler_task_to_await and not scheduler_task_to_await.done():
|
|
275
|
-
logger.info("Waiting for FlockScheduler task to complete...")
|
|
276
|
-
try:
|
|
277
|
-
await asyncio.wait_for(scheduler_task_to_await, timeout=10.0) # Wait for graceful exit
|
|
278
|
-
logger.info("FlockScheduler task completed gracefully.")
|
|
279
|
-
except asyncio.TimeoutError:
|
|
280
|
-
logger.warning("FlockScheduler task did not complete in time, cancelling.")
|
|
281
|
-
scheduler_task_to_await.cancel()
|
|
282
|
-
try:
|
|
283
|
-
await scheduler_task_to_await # Await cancellation
|
|
284
|
-
except asyncio.CancelledError:
|
|
285
|
-
logger.info("FlockScheduler task cancelled.")
|
|
286
|
-
except Exception as e:
|
|
287
|
-
logger.error(f"Error during FlockScheduler task finalization: {e}", exc_info=True)
|
|
288
|
-
elif scheduler_task_to_await and scheduler_task_to_await.done():
|
|
289
|
-
logger.info("FlockScheduler task was already done.")
|
|
290
|
-
else:
|
|
291
|
-
logger.info("FlockScheduler instance found, but no running task was stored to await.")
|
|
292
|
-
else:
|
|
293
|
-
logger.info("No active FlockScheduler found to stop.")
|
|
294
|
-
|
|
295
|
-
logger.info("FastAPI application finished shutdown sequence.")
|
|
296
|
-
# --- End Scheduler Shutdown Logic ---
|
|
297
|
-
|
|
298
|
-
app = FastAPI(title="Flock Web UI & API", lifespan=lifespan, docs_url="/docs",
|
|
299
|
-
openapi_url="/openapi.json", root_path=os.getenv("FLOCK_ROOT_PATH", ""))
|
|
300
|
-
|
|
301
|
-
# Add middleware for handling proxy headers (HTTPS detection)
|
|
302
|
-
# You can force HTTPS by setting FLOCK_FORCE_HTTPS=true
|
|
303
|
-
force_https = os.getenv("FLOCK_FORCE_HTTPS", "false").lower() == "true"
|
|
304
|
-
app.add_middleware(ProxyHeadersMiddleware, force_https=force_https)
|
|
305
|
-
logger.info(f"FastAPI booting complete with proxy headers middleware (force_https={force_https}).")
|
|
306
|
-
|
|
307
|
-
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
308
|
-
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
|
|
309
|
-
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
310
|
-
|
|
311
|
-
# Add markdown2 filter to Jinja2 environment
|
|
312
|
-
def markdown_filter(text):
|
|
313
|
-
return markdown2.markdown(text, extras=["tables", "fenced-code-blocks"])
|
|
314
|
-
|
|
315
|
-
templates.env.filters['markdown'] = markdown_filter
|
|
316
|
-
|
|
317
|
-
core_api_router = create_api_router()
|
|
318
|
-
app.include_router(core_api_router, prefix="/api", tags=["Flock API Core"])
|
|
319
|
-
app.include_router(flock_management.router, prefix="/ui/api/flock", tags=["UI Flock Management"])
|
|
320
|
-
app.include_router(agent_management.router, prefix="/ui/api/flock", tags=["UI Agent Management"])
|
|
321
|
-
app.include_router(execution.router, prefix="/ui/api/flock", tags=["UI Execution"])
|
|
322
|
-
app.include_router(registry_viewer.router, prefix="/ui/api/registry", tags=["UI Registry"])
|
|
323
|
-
|
|
324
|
-
# --- Share Link API Models and Endpoint ---
|
|
325
|
-
class CreateShareLinkRequest(BaseModel):
|
|
326
|
-
agent_name: str
|
|
327
|
-
|
|
328
|
-
class CreateShareLinkResponse(BaseModel):
|
|
329
|
-
share_url: str
|
|
330
|
-
|
|
331
|
-
@app.post("/api/v1/share/link", response_model=CreateShareLinkResponse, tags=["UI Sharing"])
|
|
332
|
-
async def create_share_link(
|
|
333
|
-
request: Request,
|
|
334
|
-
request_data: CreateShareLinkRequest,
|
|
335
|
-
store: SharedLinkStoreInterface = Depends(get_shared_link_store)
|
|
336
|
-
):
|
|
337
|
-
"""Creates a new shareable link for an agent."""
|
|
338
|
-
share_id = uuid.uuid4().hex
|
|
339
|
-
agent_name = request_data.agent_name
|
|
340
|
-
|
|
341
|
-
if not agent_name: # Basic validation
|
|
342
|
-
raise HTTPException(status_code=400, detail="Agent name cannot be empty.")
|
|
343
|
-
|
|
344
|
-
current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
|
|
345
|
-
current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
|
|
346
|
-
|
|
347
|
-
if not current_flock_instance or not current_flock_filename:
|
|
348
|
-
logger.error("Cannot create share link: No Flock is currently loaded in the application state.")
|
|
349
|
-
raise HTTPException(status_code=400, detail="No Flock loaded. Cannot create share link.")
|
|
350
|
-
|
|
351
|
-
if agent_name not in current_flock_instance.agents:
|
|
352
|
-
logger.error(f"Agent '{agent_name}' not found in currently loaded Flock '{current_flock_instance.name}'.")
|
|
353
|
-
raise HTTPException(status_code=404, detail=f"Agent '{agent_name}' not found in current Flock.")
|
|
354
|
-
|
|
355
|
-
try:
|
|
356
|
-
flock_file_path = FLOCK_FILES_DIR / current_flock_filename
|
|
357
|
-
if not flock_file_path.is_file():
|
|
358
|
-
logger.warning(f"Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
|
|
359
|
-
flock_definition_str = current_flock_instance.to_yaml()
|
|
360
|
-
else:
|
|
361
|
-
flock_definition_str = flock_file_path.read_text()
|
|
362
|
-
except Exception as e:
|
|
363
|
-
logger.error(f"Failed to get flock definition for sharing: {e}", exc_info=True)
|
|
364
|
-
raise HTTPException(status_code=500, detail="Could not retrieve Flock definition for sharing.")
|
|
365
|
-
|
|
366
|
-
config = SharedLinkConfig(
|
|
367
|
-
share_id=share_id,
|
|
368
|
-
agent_name=agent_name,
|
|
369
|
-
flock_definition=flock_definition_str
|
|
370
|
-
)
|
|
371
|
-
try:
|
|
372
|
-
await store.save_config(config)
|
|
373
|
-
share_url = f"/ui/shared-run/{share_id}" # Relative URL for client-side navigation
|
|
374
|
-
logger.info(f"Created share link for agent '{agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {share_url}")
|
|
375
|
-
return CreateShareLinkResponse(share_url=share_url)
|
|
376
|
-
except Exception as e:
|
|
377
|
-
logger.error(f"Failed to create share link for agent '{agent_name}': {e}", exc_info=True)
|
|
378
|
-
raise HTTPException(status_code=500, detail=f"Failed to create share link: {e!s}")
|
|
379
|
-
|
|
380
|
-
# --- End Share Link API ---
|
|
381
|
-
|
|
382
|
-
# --- HTMX Endpoint for Generating Share Link Snippet ---
|
|
383
|
-
@app.post("/ui/htmx/share/generate-link", response_class=HTMLResponse, tags=["UI Sharing HTMX"])
|
|
384
|
-
async def htmx_generate_share_link(
|
|
385
|
-
request: Request,
|
|
386
|
-
start_agent_name: str | None = Form(None),
|
|
387
|
-
store: SharedLinkStoreInterface = Depends(get_shared_link_store)
|
|
388
|
-
):
|
|
389
|
-
if not start_agent_name:
|
|
390
|
-
logger.warning("HTMX generate share link: Agent name not provided.")
|
|
391
|
-
return templates.TemplateResponse(
|
|
392
|
-
"partials/_share_link_snippet.html",
|
|
393
|
-
{"request": request, "error_message": "No agent selected to share."}
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
|
|
397
|
-
current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
|
|
398
|
-
|
|
399
|
-
if not current_flock_instance or not current_flock_filename:
|
|
400
|
-
logger.error("HTMX: Cannot create share link: No Flock is currently loaded.")
|
|
401
|
-
return templates.TemplateResponse(
|
|
402
|
-
"partials/_share_link_snippet.html",
|
|
403
|
-
{"request": request, "error_message": "No Flock loaded. Cannot create share link."}
|
|
404
|
-
)
|
|
405
|
-
|
|
406
|
-
if start_agent_name not in current_flock_instance.agents:
|
|
407
|
-
logger.error(f"HTMX: Agent '{start_agent_name}' not found in Flock '{current_flock_instance.name}'.")
|
|
408
|
-
return templates.TemplateResponse(
|
|
409
|
-
"partials/_share_link_snippet.html",
|
|
410
|
-
{"request": request, "error_message": f"Agent '{start_agent_name}' not found in current Flock."}
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
try:
|
|
414
|
-
flock_file_path = FLOCK_FILES_DIR / current_flock_filename
|
|
415
|
-
if not flock_file_path.is_file():
|
|
416
|
-
logger.warning(f"HTMX: Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
|
|
417
|
-
flock_definition_str = current_flock_instance.to_yaml()
|
|
418
|
-
else:
|
|
419
|
-
flock_definition_str = flock_file_path.read_text()
|
|
420
|
-
except Exception as e:
|
|
421
|
-
logger.error(f"HTMX: Failed to get flock definition for sharing: {e}", exc_info=True)
|
|
422
|
-
return templates.TemplateResponse(
|
|
423
|
-
"partials/_share_link_snippet.html",
|
|
424
|
-
{"request": request, "error_message": "Could not retrieve Flock definition for sharing."}
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
share_id = uuid.uuid4().hex
|
|
428
|
-
config = SharedLinkConfig(
|
|
429
|
-
share_id=share_id,
|
|
430
|
-
agent_name=start_agent_name,
|
|
431
|
-
flock_definition=flock_definition_str
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
try:
|
|
435
|
-
await store.save_config(config)
|
|
436
|
-
base_url = str(request.base_url)
|
|
437
|
-
full_share_url = f"{base_url.rstrip('/')}/ui/shared-run/{share_id}"
|
|
438
|
-
|
|
439
|
-
logger.info(f"HTMX: Generated share link for agent '{start_agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {full_share_url}")
|
|
440
|
-
return templates.TemplateResponse(
|
|
441
|
-
"partials/_share_link_snippet.html",
|
|
442
|
-
{"request": request, "share_url": full_share_url, "flock_name": current_flock_instance.name, "agent_name": start_agent_name}
|
|
443
|
-
)
|
|
444
|
-
except Exception as e:
|
|
445
|
-
logger.error(f"HTMX: Failed to create share link for agent '{start_agent_name}': {e}", exc_info=True)
|
|
446
|
-
return templates.TemplateResponse(
|
|
447
|
-
"partials/_share_link_snippet.html",
|
|
448
|
-
{"request": request, "error_message": f"Could not generate link: {e!s}"}
|
|
449
|
-
)
|
|
450
|
-
# --- End HTMX Endpoint ---
|
|
451
|
-
|
|
452
|
-
# --- HTMX Endpoint for Generating SHARED CHAT Link Snippet ---
|
|
453
|
-
@app.post("/ui/htmx/share/chat/generate-link", response_class=HTMLResponse, tags=["UI Sharing HTMX"])
|
|
454
|
-
async def htmx_generate_share_chat_link(
|
|
455
|
-
request: Request,
|
|
456
|
-
agent_name: str | None = Form(None), # This is the chat agent
|
|
457
|
-
message_key: str | None = Form(None), # Changed default to None
|
|
458
|
-
history_key: str | None = Form(None), # Changed default to None
|
|
459
|
-
response_key: str | None = Form(None), # Changed default to None
|
|
460
|
-
store: SharedLinkStoreInterface = Depends(get_shared_link_store)
|
|
461
|
-
):
|
|
462
|
-
if not agent_name:
|
|
463
|
-
logger.warning("HTMX generate share chat link: Agent name not provided.")
|
|
464
|
-
return templates.TemplateResponse(
|
|
465
|
-
"partials/_share_chat_link_snippet.html", # Will create this template
|
|
466
|
-
{"request": request, "error_message": "No agent selected for chat sharing."}
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
|
|
470
|
-
current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
|
|
471
|
-
|
|
472
|
-
if not current_flock_instance or not current_flock_filename:
|
|
473
|
-
logger.error("HTMX Chat Share: Cannot create share link: No Flock is currently loaded.")
|
|
474
|
-
return templates.TemplateResponse(
|
|
475
|
-
"partials/_share_chat_link_snippet.html",
|
|
476
|
-
{"request": request, "error_message": "No Flock loaded. Cannot create share link."}
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
if agent_name not in current_flock_instance.agents:
|
|
480
|
-
logger.error(f"HTMX Chat Share: Agent '{agent_name}' not found in Flock '{current_flock_instance.name}'.")
|
|
481
|
-
return templates.TemplateResponse(
|
|
482
|
-
"partials/_share_chat_link_snippet.html",
|
|
483
|
-
{"request": request, "error_message": f"Agent '{agent_name}' not found in current Flock."}
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
try:
|
|
487
|
-
flock_file_path = FLOCK_FILES_DIR / current_flock_filename
|
|
488
|
-
if not flock_file_path.is_file():
|
|
489
|
-
logger.warning(f"HTMX Chat Share: Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
|
|
490
|
-
flock_definition_str = current_flock_instance.to_yaml()
|
|
491
|
-
else:
|
|
492
|
-
flock_definition_str = flock_file_path.read_text()
|
|
493
|
-
except Exception as e:
|
|
494
|
-
logger.error(f"HTMX Chat Share: Failed to get flock definition for sharing: {e}", exc_info=True)
|
|
495
|
-
return templates.TemplateResponse(
|
|
496
|
-
"partials/_share_chat_link_snippet.html",
|
|
497
|
-
{"request": request, "error_message": "Could not retrieve Flock definition for sharing."}
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
share_id = uuid.uuid4().hex
|
|
501
|
-
|
|
502
|
-
# Explicitly convert empty strings from form to None for optional keys
|
|
503
|
-
actual_message_key = message_key if message_key else None
|
|
504
|
-
actual_history_key = history_key if history_key else None
|
|
505
|
-
actual_response_key = response_key if response_key else None
|
|
506
|
-
|
|
507
|
-
config = SharedLinkConfig(
|
|
508
|
-
share_id=share_id,
|
|
509
|
-
agent_name=agent_name, # agent_name from form is the chat agent
|
|
510
|
-
flock_definition=flock_definition_str,
|
|
511
|
-
share_type="chat",
|
|
512
|
-
chat_message_key=actual_message_key,
|
|
513
|
-
chat_history_key=actual_history_key,
|
|
514
|
-
chat_response_key=actual_response_key
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
try:
|
|
518
|
-
await store.save_config(config)
|
|
519
|
-
base_url = str(request.base_url)
|
|
520
|
-
# Link to the new /chat/shared/{share_id} endpoint
|
|
521
|
-
full_share_url = f"{base_url.rstrip('/')}/chat/shared/{share_id}"
|
|
522
|
-
|
|
523
|
-
logger.info(f"HTMX: Generated share CHAT link for agent '{agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {full_share_url}")
|
|
524
|
-
return templates.TemplateResponse(
|
|
525
|
-
"partials/_share_chat_link_snippet.html", # Will create this template
|
|
526
|
-
{"request": request, "share_url": full_share_url, "flock_name": current_flock_instance.name, "agent_name": agent_name}
|
|
527
|
-
)
|
|
528
|
-
except Exception as e:
|
|
529
|
-
logger.error(f"HTMX Chat Share: Failed to create share link for agent '{agent_name}': {e}", exc_info=True)
|
|
530
|
-
return templates.TemplateResponse(
|
|
531
|
-
"partials/_share_chat_link_snippet.html",
|
|
532
|
-
{"request": request, "error_message": f"Could not generate chat link: {e!s}"}
|
|
533
|
-
)
|
|
534
|
-
|
|
535
|
-
# --- Route for Shared Run Page ---
|
|
536
|
-
@app.get("/ui/shared-run/{share_id}", response_class=HTMLResponse, tags=["UI Sharing"])
|
|
537
|
-
async def page_shared_run(
|
|
538
|
-
request: Request,
|
|
539
|
-
share_id: str,
|
|
540
|
-
store: SharedLinkStoreInterface = Depends(get_shared_link_store),
|
|
541
|
-
):
|
|
542
|
-
logger.info(f"Accessed shared run page with share_id: {share_id}")
|
|
543
|
-
shared_config = await store.get_config(share_id)
|
|
544
|
-
|
|
545
|
-
if not shared_config:
|
|
546
|
-
logger.warning(f"Share ID {share_id} not found.")
|
|
547
|
-
return templates.TemplateResponse(
|
|
548
|
-
"error_page.html",
|
|
549
|
-
{"request": request, "error_title": "Link Not Found", "error_message": "The shared link does not exist or may have expired."},
|
|
550
|
-
status_code=404
|
|
551
|
-
)
|
|
552
|
-
|
|
553
|
-
agent_name_from_link = shared_config.agent_name
|
|
554
|
-
flock_definition_str = shared_config.flock_definition
|
|
555
|
-
context: dict[str, Any] = {"request": request, "is_shared_run_page": True, "share_id": share_id}
|
|
556
|
-
|
|
557
|
-
try:
|
|
558
|
-
from flock.core.flock import Flock as ConcreteFlock
|
|
559
|
-
loaded_flock = ConcreteFlock.from_yaml(flock_definition_str)
|
|
560
|
-
|
|
561
|
-
# Store the loaded_flock instance in app.state for later retrieval
|
|
562
|
-
if not hasattr(request.app.state, 'shared_flocks'):
|
|
563
|
-
request.app.state.shared_flocks = {}
|
|
564
|
-
request.app.state.shared_flocks[share_id] = loaded_flock
|
|
565
|
-
logger.info(f"Shared Run Page: Stored Flock instance for share_id {share_id} in app.state.")
|
|
566
|
-
|
|
567
|
-
context["flock"] = loaded_flock
|
|
568
|
-
context["selected_agent_name"] = agent_name_from_link # For pre-selection & hidden field
|
|
569
|
-
# flock_definition_str is no longer needed in the template for a hidden field if we reuse the instance
|
|
570
|
-
# context["flock_definition_str"] = flock_definition_str
|
|
571
|
-
logger.info(f"Shared Run Page: Loaded Flock '{loaded_flock.name}' for agent '{agent_name_from_link}'.")
|
|
572
|
-
|
|
573
|
-
if agent_name_from_link not in loaded_flock.agents:
|
|
574
|
-
context["error_message"] = f"Agent '{agent_name_from_link}' not found in the shared Flock definition."
|
|
575
|
-
logger.warning(context["error_message"])
|
|
576
|
-
else:
|
|
577
|
-
agent = loaded_flock.agents[agent_name_from_link]
|
|
578
|
-
input_fields = []
|
|
579
|
-
if agent.input and isinstance(agent.input, str):
|
|
580
|
-
try:
|
|
581
|
-
parsed_spec = parse_schema(agent.input) # parse_schema is imported at top of main.py
|
|
582
|
-
for name, type_str, description in parsed_spec:
|
|
583
|
-
field_info = {"name": name, "type": type_str.lower(), "description": description or ""}
|
|
584
|
-
if "bool" in field_info["type"]: field_info["html_type"] = "checkbox"
|
|
585
|
-
elif "int" in field_info["type"] or "float" in field_info["type"]: field_info["html_type"] = "number"
|
|
586
|
-
elif "list" in field_info["type"] or "dict" in field_info["type"]:
|
|
587
|
-
field_info["html_type"] = "textarea"; field_info["placeholder"] = f"Enter JSON for {field_info['type']}"
|
|
588
|
-
else: field_info["html_type"] = "text"
|
|
589
|
-
input_fields.append(field_info)
|
|
590
|
-
context["input_fields"] = input_fields
|
|
591
|
-
except Exception as e_parse:
|
|
592
|
-
logger.error(f"Shared Run Page: Error parsing input for '{agent_name_from_link}': {e_parse}", exc_info=True)
|
|
593
|
-
context["error_message"] = f"Could not parse inputs for agent '{agent_name_from_link}'."
|
|
594
|
-
else:
|
|
595
|
-
context["input_fields"] = [] # Agent has no inputs defined
|
|
596
|
-
|
|
597
|
-
except Exception as e_load:
|
|
598
|
-
logger.error(f"Shared Run Page: Failed to load Flock from definition for share_id {share_id}: {e_load}", exc_info=True)
|
|
599
|
-
context["error_message"] = f"Fatal: Could not load the shared Flock configuration: {e_load!s}"
|
|
600
|
-
context["flock"] = None
|
|
601
|
-
context["selected_agent_name"] = agent_name_from_link # Still pass for potential error display
|
|
602
|
-
context["input_fields"] = []
|
|
603
|
-
# context["flock_definition_str"] = flock_definition_str # Not needed if not sent to template
|
|
604
|
-
|
|
605
|
-
try:
|
|
606
|
-
current_theme_name = get_current_theme_name()
|
|
607
|
-
context["theme_css"] = generate_theme_css_web(current_theme_name)
|
|
608
|
-
context["active_theme_name"] = current_theme_name or DEFAULT_THEME_NAME
|
|
609
|
-
except Exception as e_theme:
|
|
610
|
-
logger.error(f"Shared Run Page: Error generating theme: {e_theme}", exc_info=True)
|
|
611
|
-
context["theme_css"] = ""
|
|
612
|
-
context["active_theme_name"] = DEFAULT_THEME_NAME
|
|
613
|
-
|
|
614
|
-
# The shared_run_page.html will now be a simple wrapper that includes _execution_form.html
|
|
615
|
-
return templates.TemplateResponse("shared_run_page.html", context)
|
|
616
|
-
|
|
617
|
-
# --- End Route for Shared Run Page ---
|
|
618
|
-
|
|
619
|
-
def generate_theme_css_web(theme_name: str | None) -> str:
|
|
620
|
-
if not THEME_LOADER_AVAILABLE or THEMES_DIR is None: return ""
|
|
621
|
-
|
|
622
|
-
chosen_theme_name_input = theme_name or get_current_theme_name() or DEFAULT_THEME_NAME
|
|
623
|
-
|
|
624
|
-
# Sanitize the input to get only the filename component
|
|
625
|
-
sanitized_name_part = Path(chosen_theme_name_input).name
|
|
626
|
-
# Ensure we have a stem
|
|
627
|
-
theme_stem_candidate = sanitized_name_part
|
|
628
|
-
if theme_stem_candidate.endswith(".toml"):
|
|
629
|
-
theme_stem_candidate = theme_stem_candidate[:-5]
|
|
630
|
-
|
|
631
|
-
effective_theme_filename = f"{theme_stem_candidate}.toml"
|
|
632
|
-
_theme_to_load_stem = theme_stem_candidate # This will be the name of the theme we attempt to load
|
|
633
|
-
|
|
634
|
-
try:
|
|
635
|
-
resolved_themes_dir = THEMES_DIR.resolve(strict=True) # Ensure THEMES_DIR itself is valid
|
|
636
|
-
prospective_theme_path = resolved_themes_dir / effective_theme_filename
|
|
637
|
-
|
|
638
|
-
# Resolve the prospective path
|
|
639
|
-
resolved_theme_path = prospective_theme_path.resolve()
|
|
640
|
-
|
|
641
|
-
# Validate:
|
|
642
|
-
# 1. Path is still within the resolved THEMES_DIR
|
|
643
|
-
# 2. The final filename component of the resolved path matches the intended filename
|
|
644
|
-
# (guards against symlinks or normalization changing the name unexpectedly)
|
|
645
|
-
# 3. The file exists
|
|
646
|
-
if (
|
|
647
|
-
str(resolved_theme_path).startswith(str(resolved_themes_dir)) and
|
|
648
|
-
resolved_theme_path.name == effective_theme_filename and
|
|
649
|
-
resolved_theme_path.is_file() # is_file checks existence too
|
|
650
|
-
):
|
|
651
|
-
theme_path = resolved_theme_path
|
|
652
|
-
else:
|
|
653
|
-
logger.warning(
|
|
654
|
-
f"Validation failed or theme '{effective_theme_filename}' not found in '{resolved_themes_dir}'. "
|
|
655
|
-
f"Attempted path: '{prospective_theme_path}'. Resolved to: '{resolved_theme_path}'. "
|
|
656
|
-
f"Falling back to default theme: {DEFAULT_THEME_NAME}.toml"
|
|
657
|
-
)
|
|
658
|
-
_theme_to_load_stem = DEFAULT_THEME_NAME
|
|
659
|
-
theme_path = resolved_themes_dir / f"{DEFAULT_THEME_NAME}.toml"
|
|
660
|
-
if not theme_path.is_file():
|
|
661
|
-
logger.error(f"Default theme file '{theme_path}' not found. No theme CSS will be generated.")
|
|
662
|
-
return ""
|
|
663
|
-
except FileNotFoundError: # THEMES_DIR does not exist
|
|
664
|
-
logger.error(f"Themes directory '{THEMES_DIR}' not found. Falling back to default theme.")
|
|
665
|
-
_theme_to_load_stem = DEFAULT_THEME_NAME
|
|
666
|
-
# Attempt to use a conceptual default path if THEMES_DIR was bogus, though it's unlikely to succeed
|
|
667
|
-
theme_path = Path(f"{DEFAULT_THEME_NAME}.toml") # This won't be in THEMES_DIR if THEMES_DIR is bad
|
|
668
|
-
if not theme_path.exists(): # Check existence without assuming a base directory
|
|
669
|
-
logger.error(f"Default theme file '{DEFAULT_THEME_NAME}.toml' not found at root or THEMES_DIR is inaccessible. No theme CSS.")
|
|
670
|
-
return ""
|
|
671
|
-
except Exception as e:
|
|
672
|
-
logger.error(f"Error during theme path resolution for '{effective_theme_filename}': {e}. Falling back to default.")
|
|
673
|
-
_theme_to_load_stem = DEFAULT_THEME_NAME
|
|
674
|
-
theme_path = THEMES_DIR / f"{DEFAULT_THEME_NAME}.toml" if THEMES_DIR else Path(f"{DEFAULT_THEME_NAME}.toml")
|
|
675
|
-
if not theme_path.exists():
|
|
676
|
-
logger.error(f"Default theme file '{theme_path}' not found after error. No theme CSS.")
|
|
677
|
-
return ""
|
|
678
|
-
|
|
679
|
-
try:
|
|
680
|
-
theme_dict = load_theme_from_file(str(theme_path))
|
|
681
|
-
logger.debug(f"Successfully loaded theme '{_theme_to_load_stem}' from '{theme_path}'")
|
|
682
|
-
except Exception as e:
|
|
683
|
-
logger.error(f"Error loading theme file '{theme_path}' (intended: '{_theme_to_load_stem}.toml'): {e}")
|
|
684
|
-
return ""
|
|
685
|
-
|
|
686
|
-
pico_vars = alacritty_to_pico(theme_dict)
|
|
687
|
-
if not pico_vars: return ""
|
|
688
|
-
css_rules = [f" {name}: {value};" for name, value in pico_vars.items()]
|
|
689
|
-
css_string = ":root {\n" + "\n".join(css_rules) + "\n}"
|
|
690
|
-
return css_string
|
|
691
|
-
|
|
692
|
-
def get_base_context_web(
|
|
693
|
-
request: Request, error: str = None, success: str = None, ui_mode: str = "standalone"
|
|
694
|
-
) -> dict:
|
|
695
|
-
flock_instance_from_state: Flock | None = getattr(request.app.state, "flock_instance", None)
|
|
696
|
-
current_flock_filename_from_state: str | None = getattr(request.app.state, "flock_filename", None)
|
|
697
|
-
theme_name = get_current_theme_name()
|
|
698
|
-
theme_css = generate_theme_css_web(theme_name)
|
|
699
|
-
|
|
700
|
-
return {
|
|
701
|
-
"request": request,
|
|
702
|
-
"current_flock": flock_instance_from_state,
|
|
703
|
-
"current_filename": current_flock_filename_from_state,
|
|
704
|
-
"error_message": error,
|
|
705
|
-
"success_message": success,
|
|
706
|
-
"ui_mode": ui_mode,
|
|
707
|
-
"theme_css": theme_css,
|
|
708
|
-
"active_theme_name": theme_name,
|
|
709
|
-
"chat_enabled": getattr(request.app.state, "chat_enabled", False), # Reverted to app.state
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
@app.get("/", response_class=HTMLResponse, tags=["UI Pages"])
|
|
713
|
-
async def page_dashboard(
|
|
714
|
-
request: Request, error: str = None, success: str = None, ui_mode: str = Query(None)
|
|
715
|
-
):
|
|
716
|
-
# Handle initial redirect if flagged during app startup
|
|
717
|
-
if getattr(request.app.state, "initial_redirect_to_chat", False):
|
|
718
|
-
logger.info("Initial redirect to CHAT page triggered from dashboard (FLOCK_START_MODE='chat').")
|
|
719
|
-
# Use url_for to respect the root_path setting
|
|
720
|
-
chat_url = str(request.url_for("page_chat"))
|
|
721
|
-
return RedirectResponse(url=chat_url, status_code=307)
|
|
722
|
-
|
|
723
|
-
effective_ui_mode = ui_mode
|
|
724
|
-
flock_is_preloaded = hasattr(request.app.state, "flock_instance") and request.app.state.flock_instance is not None
|
|
725
|
-
|
|
726
|
-
if effective_ui_mode is None:
|
|
727
|
-
effective_ui_mode = "scoped" if flock_is_preloaded else "standalone"
|
|
728
|
-
if effective_ui_mode == "scoped":
|
|
729
|
-
# Manually construct URL with root_path to ensure it works with proxy setups
|
|
730
|
-
root_path = request.scope.get("root_path", "")
|
|
731
|
-
redirect_url = f"{root_path}/?ui_mode=scoped&initial_load=true"
|
|
732
|
-
logger.info(f"Dashboard redirect: {redirect_url} (root_path: '{root_path}')")
|
|
733
|
-
return RedirectResponse(url=redirect_url, status_code=307)
|
|
734
|
-
|
|
735
|
-
if effective_ui_mode == "standalone" and flock_is_preloaded:
|
|
736
|
-
clear_current_flock_service(request.app.state) # Pass app.state
|
|
737
|
-
logger.info("Switched to standalone mode, cleared preloaded Flock instance from app.state.")
|
|
738
|
-
|
|
739
|
-
context = get_base_context_web(request, error, success, effective_ui_mode)
|
|
740
|
-
flock_in_state = hasattr(request.app.state, "flock_instance") and request.app.state.flock_instance is not None
|
|
741
|
-
|
|
742
|
-
if effective_ui_mode == "scoped":
|
|
743
|
-
context["initial_content_url"] = str(request.url_for("htmx_get_execution_view_container")) if flock_in_state else str(request.url_for("htmx_scoped_no_flock_view"))
|
|
744
|
-
else:
|
|
745
|
-
context["initial_content_url"] = str(request.url_for("htmx_get_load_flock_view"))
|
|
746
|
-
return templates.TemplateResponse("base.html", context)
|
|
747
|
-
|
|
748
|
-
@app.get("/ui/editor/{section:path}", response_class=HTMLResponse, tags=["UI Pages"])
|
|
749
|
-
async def page_editor_section(
|
|
750
|
-
request: Request, section: str, success: str = None, error: str = None, ui_mode: str = Query("standalone")
|
|
751
|
-
):
|
|
752
|
-
flock_instance_from_state: Flock | None = getattr(request.app.state, "flock_instance", None)
|
|
753
|
-
if not flock_instance_from_state:
|
|
754
|
-
err_msg = "No flock loaded. Please load or create a flock first."
|
|
755
|
-
# Use url_for to respect the root_path setting
|
|
756
|
-
redirect_url = str(request.url_for("page_dashboard").include_query_params(error=err_msg))
|
|
757
|
-
if ui_mode == "scoped":
|
|
758
|
-
redirect_url = str(request.url_for("page_dashboard").include_query_params(error=err_msg, ui_mode="scoped"))
|
|
759
|
-
return RedirectResponse(url=redirect_url, status_code=303)
|
|
760
|
-
|
|
761
|
-
context = get_base_context_web(request, error, success, ui_mode)
|
|
762
|
-
root_path = request.scope.get("root_path", "")
|
|
763
|
-
content_map = {
|
|
764
|
-
"properties": f"{root_path}/ui/api/flock/htmx/flock-properties-form",
|
|
765
|
-
"agents": f"{root_path}/ui/htmx/agent-manager-view",
|
|
766
|
-
"execute": f"{root_path}/ui/htmx/execution-view-container"
|
|
767
|
-
}
|
|
768
|
-
context["initial_content_url"] = content_map.get(section, f"{root_path}/ui/htmx/load-flock-view")
|
|
769
|
-
if section not in content_map: context["error_message"] = "Invalid editor section."
|
|
770
|
-
return templates.TemplateResponse("base.html", context)
|
|
771
|
-
|
|
772
|
-
@app.get("/ui/registry", response_class=HTMLResponse, tags=["UI Pages"])
|
|
773
|
-
async def page_registry(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
|
|
774
|
-
context = get_base_context_web(request, error, success, ui_mode)
|
|
775
|
-
root_path = request.scope.get("root_path", "")
|
|
776
|
-
context["initial_content_url"] = f"{root_path}/ui/htmx/registry-viewer"
|
|
777
|
-
return templates.TemplateResponse("base.html", context)
|
|
778
|
-
|
|
779
|
-
@app.get("/ui/create", response_class=HTMLResponse, tags=["UI Pages"])
|
|
780
|
-
async def page_create(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
|
|
781
|
-
clear_current_flock_service(request.app.state) # Pass app.state
|
|
782
|
-
context = get_base_context_web(request, error, success, "standalone")
|
|
783
|
-
root_path = request.scope.get("root_path", "")
|
|
784
|
-
context["initial_content_url"] = f"{root_path}/ui/htmx/create-flock-form"
|
|
785
|
-
return templates.TemplateResponse("base.html", context)
|
|
786
|
-
|
|
787
|
-
@app.get("/ui/htmx/sidebar", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
788
|
-
async def htmx_get_sidebar(request: Request, ui_mode: str = Query("standalone")):
|
|
789
|
-
return templates.TemplateResponse("partials/_sidebar.html", get_base_context_web(request, ui_mode=ui_mode))
|
|
790
|
-
|
|
791
|
-
@app.get("/ui/htmx/header-flock-status", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
792
|
-
async def htmx_get_header_flock_status(request: Request, ui_mode: str = Query("standalone")):
|
|
793
|
-
return templates.TemplateResponse("partials/_header_flock_status.html", get_base_context_web(request, ui_mode=ui_mode))
|
|
794
|
-
|
|
795
|
-
@app.get("/ui/htmx/load-flock-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
796
|
-
async def htmx_get_load_flock_view(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
|
|
797
|
-
return templates.TemplateResponse("partials/_load_manager_view.html", get_base_context_web(request, error, success, ui_mode))
|
|
798
|
-
|
|
799
|
-
@app.get("/ui/htmx/dashboard-flock-file-list", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
800
|
-
async def htmx_get_dashboard_flock_file_list_partial(request: Request):
|
|
801
|
-
return templates.TemplateResponse("partials/_dashboard_flock_file_list.html", {"request": request, "flock_files": get_available_flock_files()})
|
|
802
|
-
|
|
803
|
-
@app.get("/ui/htmx/dashboard-default-action-pane", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
804
|
-
async def htmx_get_dashboard_default_action_pane(request: Request):
|
|
805
|
-
return HTMLResponse("""<article style="text-align:center; margin-top: 2rem; border: none; background: transparent;"><p>Select a Flock from the list to view its details and load it into the editor.</p><hr><p>Or, create a new Flock or upload an existing one using the "Create New Flock" option in the sidebar.</p></article>""")
|
|
806
|
-
|
|
807
|
-
@app.get("/ui/htmx/dashboard-flock-properties-preview/{filename}", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
808
|
-
async def htmx_get_dashboard_flock_properties_preview(request: Request, filename: str):
|
|
809
|
-
preview_flock_data = get_flock_preview_service(filename)
|
|
810
|
-
return templates.TemplateResponse("partials/_dashboard_flock_properties_preview.html", {"request": request, "selected_filename": filename, "preview_flock": preview_flock_data})
|
|
811
|
-
|
|
812
|
-
@app.get("/ui/htmx/create-flock-form", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
813
|
-
async def htmx_get_create_flock_form(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
|
|
814
|
-
return templates.TemplateResponse("partials/_create_flock_form.html", get_base_context_web(request, error, success, ui_mode))
|
|
815
|
-
|
|
816
|
-
@app.get("/ui/htmx/agent-manager-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
817
|
-
async def htmx_get_agent_manager_view(request: Request):
|
|
818
|
-
context = get_base_context_web(request) # This gets flock from app.state
|
|
819
|
-
if not context.get("current_flock"): # Check if flock exists in the context
|
|
820
|
-
return HTMLResponse("<article class='error'><p>No flock loaded. Cannot manage agents.</p></article>")
|
|
821
|
-
# Pass the 'current_flock' from the context to the template as 'flock'
|
|
822
|
-
return templates.TemplateResponse(
|
|
823
|
-
"partials/_agent_manager_view.html",
|
|
824
|
-
{"request": request, "flock": context.get("current_flock")}
|
|
825
|
-
)
|
|
826
|
-
|
|
827
|
-
@app.get("/ui/htmx/registry-viewer", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
828
|
-
async def htmx_get_registry_viewer(request: Request):
|
|
829
|
-
return templates.TemplateResponse("partials/_registry_viewer_content.html", get_base_context_web(request))
|
|
830
|
-
|
|
831
|
-
@app.get("/ui/htmx/execution-view-container", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
832
|
-
async def htmx_get_execution_view_container(request: Request):
|
|
833
|
-
context = get_base_context_web(request)
|
|
834
|
-
if not context.get("current_flock"): return HTMLResponse("<article class='error'><p>No Flock loaded. Cannot execute.</p></article>")
|
|
835
|
-
return templates.TemplateResponse("partials/_execution_view_container.html", context)
|
|
836
|
-
|
|
837
|
-
@app.get("/ui/htmx/scoped-no-flock-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
838
|
-
async def htmx_scoped_no_flock_view(request: Request):
|
|
839
|
-
return HTMLResponse("""<article style="text-align:center; margin-top: 2rem; border: none; background: transparent;"><hgroup><h2>Scoped Flock Mode</h2><h3>No Flock Loaded</h3></hgroup><p>This UI is in a scoped mode, expecting a Flock to be pre-loaded.</p><p>Please ensure the calling application provides a Flock instance.</p></article>""")
|
|
840
|
-
|
|
841
|
-
# --- Action Routes (POST requests for UI interactions) ---
|
|
842
|
-
@app.post("/ui/load-flock-action/by-name", response_class=HTMLResponse, tags=["UI Actions"])
|
|
843
|
-
async def ui_load_flock_by_name_action(request: Request, selected_flock_filename: str = Form(...)):
|
|
844
|
-
loaded_flock = load_flock_from_file_service(selected_flock_filename, request.app.state)
|
|
845
|
-
response_headers = {}
|
|
846
|
-
ui_mode_query = request.query_params.get("ui_mode", "standalone")
|
|
847
|
-
if loaded_flock:
|
|
848
|
-
success_message_text = f"Flock '{loaded_flock.name}' loaded from '{selected_flock_filename}'."
|
|
849
|
-
response_headers["HX-Push-Url"] = "/ui/editor/execute?ui_mode=" + ui_mode_query
|
|
850
|
-
response_headers["HX-Trigger"] = create_hx_trigger_header({"flockLoaded": None, "notify": {"type": "success", "message": success_message_text}})
|
|
851
|
-
context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
|
|
852
|
-
return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
|
|
853
|
-
else:
|
|
854
|
-
error_message_text = f"Failed to load flock file '{selected_flock_filename}'."
|
|
855
|
-
response_headers["HX-Trigger"] = create_hx_trigger_header({"notify": {"type": "error", "message": error_message_text}})
|
|
856
|
-
context = get_base_context_web(request, error=error_message_text, ui_mode=ui_mode_query)
|
|
857
|
-
context["error_message_inline"] = error_message_text # For direct display in partial
|
|
858
|
-
return templates.TemplateResponse("partials/_load_manager_view.html", context, headers=response_headers)
|
|
859
|
-
|
|
860
|
-
@app.post("/ui/load-flock-action/by-upload", response_class=HTMLResponse, tags=["UI Actions"])
|
|
861
|
-
async def ui_load_flock_by_upload_action(request: Request, flock_file_upload: UploadFile = File(...)):
|
|
862
|
-
error_message_text, filename_to_load, response_headers = None, None, {}
|
|
863
|
-
ui_mode_query = request.query_params.get("ui_mode", "standalone")
|
|
864
|
-
|
|
865
|
-
if flock_file_upload and flock_file_upload.filename:
|
|
866
|
-
if not flock_file_upload.filename.endswith((".yaml", ".yml", ".flock")): error_message_text = "Invalid file type."
|
|
867
|
-
else:
|
|
868
|
-
upload_path = FLOCK_FILES_DIR / flock_file_upload.filename
|
|
869
|
-
try:
|
|
870
|
-
with upload_path.open("wb") as buffer: shutil.copyfileobj(flock_file_upload.file, buffer)
|
|
871
|
-
filename_to_load = flock_file_upload.filename
|
|
872
|
-
except Exception as e: error_message_text = f"Upload failed: {e}"
|
|
873
|
-
finally: await flock_file_upload.close()
|
|
874
|
-
else: error_message_text = "No file uploaded."
|
|
875
|
-
|
|
876
|
-
if filename_to_load and not error_message_text:
|
|
877
|
-
loaded_flock = load_flock_from_file_service(filename_to_load, request.app.state)
|
|
878
|
-
if loaded_flock:
|
|
879
|
-
success_message_text = f"Flock '{loaded_flock.name}' loaded from '{filename_to_load}'."
|
|
880
|
-
response_headers["HX-Push-Url"] = f"/ui/editor/execute?ui_mode={ui_mode_query}"
|
|
881
|
-
response_headers["HX-Trigger"] = create_hx_trigger_header({"flockLoaded": None, "flockFileListChanged": None, "notify": {"type": "success", "message": success_message_text}})
|
|
882
|
-
context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
|
|
883
|
-
return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
|
|
884
|
-
else: error_message_text = f"Failed to process uploaded '{filename_to_load}'."
|
|
885
|
-
|
|
886
|
-
final_error_msg = error_message_text or "Upload failed."
|
|
887
|
-
response_headers["HX-Trigger"] = create_hx_trigger_header({"notify": {"type": "error", "message": final_error_msg}})
|
|
888
|
-
context = get_base_context_web(request, error=final_error_msg, ui_mode=ui_mode_query)
|
|
889
|
-
return templates.TemplateResponse("partials/_create_flock_form.html", context, headers=response_headers)
|
|
890
|
-
|
|
891
|
-
@app.post("/ui/create-flock", response_class=HTMLResponse, tags=["UI Actions"])
|
|
892
|
-
async def ui_create_flock_action(request: Request, flock_name: str = Form(...), default_model: str = Form(None), description: str = Form(None)):
|
|
893
|
-
ui_mode_query = request.query_params.get("ui_mode", "standalone")
|
|
894
|
-
if not flock_name.strip():
|
|
895
|
-
context = get_base_context_web(request, error="Flock name cannot be empty.", ui_mode=ui_mode_query)
|
|
896
|
-
return templates.TemplateResponse("partials/_create_flock_form.html", context)
|
|
897
|
-
|
|
898
|
-
new_flock = create_new_flock_service(flock_name, default_model, description, request.app.state)
|
|
899
|
-
success_msg_text = f"New flock '{new_flock.name}' created. Navigating to Execute page. Configure properties and agents as needed."
|
|
900
|
-
response_headers = {"HX-Push-Url": f"/ui/editor/execute?ui_mode={ui_mode_query}", "HX-Trigger": create_hx_trigger_header({"flockLoaded": None, "notify": {"type": "success", "message": success_msg_text}})}
|
|
901
|
-
context = get_base_context_web(request, success=success_msg_text, ui_mode=ui_mode_query)
|
|
902
|
-
return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
# --- Settings Page & Endpoints ---
|
|
906
|
-
@app.get("/ui/settings", response_class=HTMLResponse, tags=["UI Pages"])
|
|
907
|
-
async def page_settings(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
|
|
908
|
-
context = get_base_context_web(request, error, success, ui_mode)
|
|
909
|
-
root_path = request.scope.get("root_path", "")
|
|
910
|
-
context["initial_content_url"] = f"{root_path}/ui/htmx/settings-view"
|
|
911
|
-
return templates.TemplateResponse("base.html", context)
|
|
912
|
-
|
|
913
|
-
def _prepare_env_vars_for_template_web():
|
|
914
|
-
env_vars_raw = load_env_file_web(); show_secrets = get_show_secrets_setting_web(env_vars_raw)
|
|
915
|
-
env_vars_list = []
|
|
916
|
-
for name, value in env_vars_raw.items():
|
|
917
|
-
if name.startswith("#") or name == "": continue
|
|
918
|
-
display_value = value if (not is_sensitive_web(name) or show_secrets) else mask_sensitive_value_web(value)
|
|
919
|
-
env_vars_list.append({"name": name, "value": display_value})
|
|
920
|
-
return env_vars_list, show_secrets
|
|
921
|
-
|
|
922
|
-
@app.get("/ui/htmx/settings-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
923
|
-
async def htmx_get_settings_view(request: Request):
|
|
924
|
-
env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
|
|
925
|
-
theme_name = get_current_theme_name()
|
|
926
|
-
themes_available = [p.stem for p in THEMES_DIR.glob("*.toml")] if THEMES_DIR and THEMES_DIR.exists() else []
|
|
927
|
-
return templates.TemplateResponse("partials/_settings_view.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets, "themes": themes_available, "current_theme": theme_name})
|
|
928
|
-
|
|
929
|
-
@app.post("/ui/htmx/toggle-show-secrets", response_class=HTMLResponse, tags=["UI Actions"])
|
|
930
|
-
async def htmx_toggle_show_secrets(request: Request):
|
|
931
|
-
env_vars_raw = load_env_file_web(); current = get_show_secrets_setting_web(env_vars_raw)
|
|
932
|
-
set_show_secrets_setting_web(not current)
|
|
933
|
-
env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
|
|
934
|
-
return templates.TemplateResponse("partials/_env_vars_table.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
|
|
935
|
-
|
|
936
|
-
@app.post("/ui/htmx/env-delete", response_class=HTMLResponse, tags=["UI Actions"])
|
|
937
|
-
async def htmx_env_delete(request: Request, var_name: str = Form(...)):
|
|
938
|
-
env_vars_raw = load_env_file_web()
|
|
939
|
-
if var_name in env_vars_raw: del env_vars_raw[var_name]; save_env_file_web(env_vars_raw)
|
|
940
|
-
env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
|
|
941
|
-
return templates.TemplateResponse("partials/_env_vars_table.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
|
|
942
|
-
|
|
943
|
-
@app.post("/ui/htmx/env-edit", response_class=HTMLResponse, tags=["UI Actions"])
|
|
944
|
-
async def htmx_env_edit(request: Request, var_name: str = Form(...)):
|
|
945
|
-
new_value = request.headers.get("HX-Prompt")
|
|
946
|
-
env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
|
|
947
|
-
if new_value is not None:
|
|
948
|
-
env_vars_raw = load_env_file_web()
|
|
949
|
-
env_vars_raw[var_name] = new_value
|
|
950
|
-
save_env_file_web(env_vars_raw)
|
|
951
|
-
env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
|
|
952
|
-
return templates.TemplateResponse("partials/_env_vars_table.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
|
|
953
|
-
|
|
954
|
-
@app.get("/ui/htmx/env-add-form", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
955
|
-
async def htmx_env_add_form(request: Request):
|
|
956
|
-
return HTMLResponse("""<form hx-post='/ui/htmx/env-add' hx-target='#env-vars-container' hx-swap='outerHTML' style='display:flex; gap:0.5rem; margin-bottom:0.5rem;'><input name='var_name' placeholder='NAME' required style='flex:2;'><input name='var_value' placeholder='VALUE' style='flex:3;'><button type='submit'>Add</button></form>""")
|
|
957
|
-
|
|
958
|
-
@app.post("/ui/htmx/env-add", response_class=HTMLResponse, tags=["UI Actions"])
|
|
959
|
-
async def htmx_env_add(request: Request, var_name: str = Form(...), var_value: str = Form("")):
|
|
960
|
-
env_vars_raw = load_env_file_web()
|
|
961
|
-
env_vars_raw[var_name] = var_value; save_env_file_web(env_vars_raw)
|
|
962
|
-
env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
|
|
963
|
-
return templates.TemplateResponse("partials/_env_vars_table.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
|
|
964
|
-
|
|
965
|
-
@app.get("/ui/htmx/theme-preview", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
966
|
-
async def htmx_theme_preview(request: Request, theme: str = Query(None)):
|
|
967
|
-
if not THEME_LOADER_AVAILABLE:
|
|
968
|
-
return HTMLResponse("<p>Theme loading functionality is not available.</p>", status_code=500)
|
|
969
|
-
if THEMES_DIR is None or not THEMES_DIR.exists():
|
|
970
|
-
return HTMLResponse("<p>Themes directory is not configured or does not exist.</p>", status_code=500)
|
|
971
|
-
|
|
972
|
-
chosen_theme_name_input = theme or get_current_theme_name() or DEFAULT_THEME_NAME
|
|
973
|
-
|
|
974
|
-
# Sanitize the input to get only the filename component
|
|
975
|
-
sanitized_name_part = Path(chosen_theme_name_input).name
|
|
976
|
-
# Ensure we have a stem
|
|
977
|
-
theme_stem_from_input = sanitized_name_part
|
|
978
|
-
if theme_stem_from_input.endswith(".toml"):
|
|
979
|
-
theme_stem_from_input = theme_stem_from_input[:-5]
|
|
980
|
-
|
|
981
|
-
theme_filename_to_load = f"{theme_stem_from_input}.toml"
|
|
982
|
-
theme_name_for_display = theme_stem_from_input # Use the sanitized stem for display/logging
|
|
983
|
-
|
|
984
|
-
try:
|
|
985
|
-
resolved_themes_dir = THEMES_DIR.resolve(strict=True)
|
|
986
|
-
theme_path_candidate = resolved_themes_dir / theme_filename_to_load
|
|
987
|
-
resolved_theme_path = theme_path_candidate.resolve()
|
|
988
|
-
|
|
989
|
-
try:
|
|
990
|
-
resolved_theme_path.relative_to(resolved_themes_dir)
|
|
991
|
-
except ValueError:
|
|
992
|
-
logger.warning(f"Invalid theme path access attempt for '{theme_name_for_display}'. "
|
|
993
|
-
f"Original input: '{chosen_theme_name_input}', Sanitized filename: '{theme_filename_to_load}', "
|
|
994
|
-
f"Attempted path: '{theme_path_candidate}', Resolved to: '{resolved_theme_path}'")
|
|
995
|
-
return HTMLResponse(f"<p>Invalid theme name or path for '{theme_name_for_display}'.</p>", status_code=400)
|
|
996
|
-
if resolved_theme_path.name != theme_filename_to_load:
|
|
997
|
-
logger.warning(f"Invalid theme filename for '{theme_name_for_display}'. "
|
|
998
|
-
f"Original input: '{chosen_theme_name_input}', Sanitized filename: '{theme_filename_to_load}', "
|
|
999
|
-
f"Attempted path: '{theme_path_candidate}', Resolved to: '{resolved_theme_path}'")
|
|
1000
|
-
return HTMLResponse(f"<p>Invalid theme name or path for '{theme_name_for_display}'.</p>", status_code=400)
|
|
1001
|
-
|
|
1002
|
-
if not resolved_theme_path.is_file():
|
|
1003
|
-
logger.info(f"Theme preview: Theme file '{theme_filename_to_load}' not found at '{resolved_theme_path}'.")
|
|
1004
|
-
return HTMLResponse(f"<p>Theme '{theme_name_for_display}' not found.</p>", status_code=404)
|
|
1005
|
-
|
|
1006
|
-
theme_path = resolved_theme_path
|
|
1007
|
-
theme_data = load_theme_from_file(str(theme_path))
|
|
1008
|
-
logger.debug(f"Successfully loaded theme '{theme_name_for_display}' for preview from '{theme_path}'")
|
|
1009
|
-
|
|
1010
|
-
except FileNotFoundError: # For THEMES_DIR.resolve(strict=True)
|
|
1011
|
-
logger.error(f"Themes directory '{THEMES_DIR}' not found during preview for '{theme_name_for_display}'.")
|
|
1012
|
-
return HTMLResponse("<p>Themes directory not found.</p>", status_code=500)
|
|
1013
|
-
except Exception as e:
|
|
1014
|
-
logger.error(f"Error loading theme '{theme_name_for_display}' for preview (path: '{theme_path_candidate if 'theme_path_candidate' in locals() else 'unknown'}'): {e}")
|
|
1015
|
-
return HTMLResponse(f"<p>Error loading theme '{theme_name_for_display}': {e}</p>", status_code=500)
|
|
1016
|
-
|
|
1017
|
-
css_vars = alacritty_to_pico(theme_data)
|
|
1018
|
-
if not css_vars:
|
|
1019
|
-
return HTMLResponse(f"<p>Could not convert theme '{theme_name_for_display}' to CSS variables.</p>")
|
|
1020
|
-
|
|
1021
|
-
css_vars_str = ":root {\n" + "\\n".join([f" {k}: {v};" for k, v in css_vars.items()]) + "\\n}"
|
|
1022
|
-
main_colors = [("Background", css_vars.get("--pico-background-color")), ("Text", css_vars.get("--pico-color")), ("Primary", css_vars.get("--pico-primary")), ("Secondary", css_vars.get("--pico-secondary")), ("Muted", css_vars.get("--pico-muted-color"))]
|
|
1023
|
-
return templates.TemplateResponse("partials/_theme_preview.html", {"request": request, "theme_name": theme_name_for_display, "css_vars_str": css_vars_str, "main_colors": main_colors})
|
|
1024
|
-
|
|
1025
|
-
@app.post("/ui/apply-theme", tags=["UI Actions"])
|
|
1026
|
-
async def apply_theme(request: Request, theme: str = Form(...)):
|
|
1027
|
-
try:
|
|
1028
|
-
from flock.webapp.app.config import set_current_theme_name
|
|
1029
|
-
set_current_theme_name(theme)
|
|
1030
|
-
headers = {"HX-Refresh": "true"}
|
|
1031
|
-
return HTMLResponse("", headers=headers)
|
|
1032
|
-
except Exception as e: return HTMLResponse(f"Failed to apply theme: {e}", status_code=500)
|
|
1033
|
-
|
|
1034
|
-
@app.get("/ui/htmx/settings/env-vars", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
1035
|
-
async def htmx_settings_env_vars(request: Request):
|
|
1036
|
-
env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
|
|
1037
|
-
return templates.TemplateResponse("partials/_settings_env_content.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
|
|
1038
|
-
|
|
1039
|
-
@app.get("/ui/htmx/settings/theme", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
1040
|
-
async def htmx_settings_theme(request: Request):
|
|
1041
|
-
theme_name = get_current_theme_name()
|
|
1042
|
-
themes_available = [p.stem for p in THEMES_DIR.glob("*.toml")] if THEMES_DIR and THEMES_DIR.exists() else []
|
|
1043
|
-
return templates.TemplateResponse("partials/_settings_theme_content.html", {"request": request, "themes": themes_available, "current_theme": theme_name})
|
|
1044
|
-
|
|
1045
|
-
@app.get("/ui/chat", response_class=HTMLResponse, tags=["UI Pages"])
|
|
1046
|
-
async def page_chat(request: Request, ui_mode: str = Query("standalone")):
|
|
1047
|
-
context = get_base_context_web(request, ui_mode=ui_mode)
|
|
1048
|
-
context["initial_content_url"] = "/ui/htmx/chat-view"
|
|
1049
|
-
return templates.TemplateResponse("base.html", context)
|
|
1050
|
-
|
|
1051
|
-
@app.get("/ui/htmx/chat-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
|
|
1052
|
-
async def htmx_get_chat_view(request: Request):
|
|
1053
|
-
# Render container partial; session handled in chat router
|
|
1054
|
-
return templates.TemplateResponse("partials/_chat_container.html", get_base_context_web(request))
|
|
1055
|
-
|
|
1056
|
-
if __name__ == "__main__":
|
|
1057
|
-
import uvicorn
|
|
1058
|
-
# Ensure the dependency injection system is initialized for standalone run
|
|
1059
|
-
temp_run_store = RunStore()
|
|
1060
|
-
# Create a default/dummy Flock instance for standalone UI testing
|
|
1061
|
-
# This allows the UI to function without being started by `Flock.start_api()`
|
|
1062
|
-
dev_flock_instance = Flock(name="DevStandaloneFlock", model="test/dummy", show_flock_banner=False)
|
|
1063
|
-
|
|
1064
|
-
set_global_flock_services(dev_flock_instance, temp_run_store)
|
|
1065
|
-
app.state.flock_instance = dev_flock_instance
|
|
1066
|
-
app.state.run_store = temp_run_store
|
|
1067
|
-
app.state.flock_filename = "development_standalone.flock.yaml"
|
|
1068
|
-
|
|
1069
|
-
logger.info("Running webapp.app.main directly for development with a dummy Flock instance.")
|
|
1070
|
-
uvicorn.run(app, host="127.0.0.1", port=8344, reload=True)
|