flock-core 0.4.511__py3-none-any.whl → 0.4.513__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/core/config/flock_agent_config.py +11 -0
- flock/core/config/scheduled_agent_config.py +40 -0
- flock/core/flock_agent.py +7 -1
- flock/core/flock_factory.py +129 -2
- flock/core/flock_scheduler.py +166 -0
- flock/core/logging/logging.py +8 -0
- flock/core/mcp/flock_mcp_server.py +30 -4
- flock/core/mcp/flock_mcp_tool_base.py +1 -1
- flock/core/mcp/mcp_client.py +57 -28
- flock/core/mcp/mcp_client_manager.py +1 -1
- flock/core/mcp/mcp_config.py +245 -9
- flock/core/mcp/types/callbacks.py +3 -5
- flock/core/mcp/types/factories.py +12 -14
- flock/core/mcp/types/handlers.py +9 -12
- flock/core/mcp/types/types.py +205 -2
- flock/mcp/servers/sse/flock_sse_server.py +21 -14
- flock/mcp/servers/streamable_http/__init__.py +0 -0
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +169 -0
- flock/mcp/servers/websockets/flock_websocket_server.py +3 -3
- flock/webapp/app/main.py +66 -11
- flock/webapp/app/services/sharing_store.py +173 -0
- flock/webapp/run.py +3 -1
- flock/webapp/templates/base.html +10 -11
- flock/webapp/templates/chat.html +7 -10
- flock/webapp/templates/chat_settings.html +3 -4
- flock/webapp/templates/flock_editor.html +1 -2
- flock/webapp/templates/index.html +1 -1
- flock/webapp/templates/partials/_agent_detail_form.html +7 -13
- flock/webapp/templates/partials/_agent_list.html +1 -2
- flock/webapp/templates/partials/_agent_manager_view.html +2 -3
- flock/webapp/templates/partials/_chat_container.html +2 -2
- flock/webapp/templates/partials/_chat_settings_form.html +6 -8
- flock/webapp/templates/partials/_create_flock_form.html +2 -4
- flock/webapp/templates/partials/_dashboard_flock_detail.html +2 -3
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +1 -2
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +2 -3
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +1 -2
- flock/webapp/templates/partials/_env_vars_table.html +2 -4
- flock/webapp/templates/partials/_execution_form.html +12 -10
- flock/webapp/templates/partials/_execution_view_container.html +2 -3
- flock/webapp/templates/partials/_flock_file_list.html +2 -3
- flock/webapp/templates/partials/_flock_properties_form.html +2 -2
- flock/webapp/templates/partials/_flock_upload_form.html +1 -2
- flock/webapp/templates/partials/_load_manager_view.html +2 -3
- flock/webapp/templates/partials/_registry_viewer_content.html +4 -5
- flock/webapp/templates/partials/_settings_env_content.html +2 -3
- flock/webapp/templates/partials/_settings_theme_content.html +2 -2
- flock/webapp/templates/partials/_settings_view.html +2 -2
- flock/webapp/templates/partials/_sidebar.html +27 -39
- flock/webapp/templates/registry_viewer.html +7 -10
- flock/webapp/templates/shared_run_page.html +7 -10
- {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/METADATA +3 -1
- {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/RECORD +56 -51
- {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/WHEEL +0 -0
- {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Shared link and feedback storage implementations supporting SQLite and Azure Table Storage."""
|
|
2
|
+
|
|
1
3
|
import logging
|
|
2
4
|
import sqlite3
|
|
3
5
|
from abc import ABC, abstractmethod
|
|
@@ -10,6 +12,17 @@ from flock.webapp.app.services.sharing_models import (
|
|
|
10
12
|
SharedLinkConfig,
|
|
11
13
|
)
|
|
12
14
|
|
|
15
|
+
# Azure Table Storage imports - will be conditionally imported
|
|
16
|
+
try:
|
|
17
|
+
from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
|
|
18
|
+
from azure.data.tables.aio import TableServiceClient
|
|
19
|
+
AZURE_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
AZURE_AVAILABLE = False
|
|
22
|
+
TableServiceClient = None
|
|
23
|
+
ResourceNotFoundError = None
|
|
24
|
+
ResourceExistsError = None
|
|
25
|
+
|
|
13
26
|
# Get a logger instance
|
|
14
27
|
logger = logging.getLogger(__name__)
|
|
15
28
|
|
|
@@ -46,6 +59,7 @@ class SQLiteSharedLinkStore(SharedLinkStoreInterface):
|
|
|
46
59
|
"""SQLite implementation for storing and retrieving shared link configurations."""
|
|
47
60
|
|
|
48
61
|
def __init__(self, db_path: str):
|
|
62
|
+
"""Initialize SQLite store with database path."""
|
|
49
63
|
self.db_path = Path(db_path)
|
|
50
64
|
self.db_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
|
|
51
65
|
logger.info(f"SQLiteSharedLinkStore initialized with db_path: {self.db_path}")
|
|
@@ -213,3 +227,162 @@ class SQLiteSharedLinkStore(SharedLinkStoreInterface):
|
|
|
213
227
|
except sqlite3.Error as e:
|
|
214
228
|
logger.error(f"SQLite error saving feedback {record.feedback_id}: {e}", exc_info=True)
|
|
215
229
|
raise
|
|
230
|
+
|
|
231
|
+
class AzureTableSharedLinkStore(SharedLinkStoreInterface):
|
|
232
|
+
"""Azure Table Storage implementation for storing and retrieving shared link configurations."""
|
|
233
|
+
|
|
234
|
+
def __init__(self, connection_string: str):
|
|
235
|
+
"""Initialize Azure Table Storage store with connection string."""
|
|
236
|
+
if not AZURE_AVAILABLE:
|
|
237
|
+
raise ImportError("Azure Table Storage dependencies not available. Install with: pip install azure-data-tables")
|
|
238
|
+
|
|
239
|
+
self.connection_string = connection_string
|
|
240
|
+
self.table_service_client = TableServiceClient.from_connection_string(connection_string)
|
|
241
|
+
self.shared_links_table_name = "flocksharedlinks"
|
|
242
|
+
self.feedback_table_name = "flockfeedback"
|
|
243
|
+
logger.info("AzureTableSharedLinkStore initialized")
|
|
244
|
+
|
|
245
|
+
async def initialize(self) -> None:
|
|
246
|
+
"""Initializes the Azure Tables (creates them if they don't exist)."""
|
|
247
|
+
try:
|
|
248
|
+
# Create shared_links table
|
|
249
|
+
try:
|
|
250
|
+
await self.table_service_client.create_table(self.shared_links_table_name)
|
|
251
|
+
logger.info(f"Created Azure Table: {self.shared_links_table_name}")
|
|
252
|
+
except ResourceExistsError:
|
|
253
|
+
logger.debug(f"Azure Table already exists: {self.shared_links_table_name}")
|
|
254
|
+
|
|
255
|
+
# Create feedback table
|
|
256
|
+
try:
|
|
257
|
+
await self.table_service_client.create_table(self.feedback_table_name)
|
|
258
|
+
logger.info(f"Created Azure Table: {self.feedback_table_name}")
|
|
259
|
+
except ResourceExistsError:
|
|
260
|
+
logger.debug(f"Azure Table already exists: {self.feedback_table_name}")
|
|
261
|
+
|
|
262
|
+
logger.info("Azure Table Storage initialized successfully")
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.error(f"Error initializing Azure Table Storage: {e}", exc_info=True)
|
|
265
|
+
raise
|
|
266
|
+
|
|
267
|
+
async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
|
|
268
|
+
"""Saves a shared link configuration to Azure Table Storage."""
|
|
269
|
+
try:
|
|
270
|
+
table_client = self.table_service_client.get_table_client(self.shared_links_table_name)
|
|
271
|
+
|
|
272
|
+
entity = {
|
|
273
|
+
"PartitionKey": "shared_links", # Use a fixed partition key for simplicity
|
|
274
|
+
"RowKey": config.share_id,
|
|
275
|
+
"share_id": config.share_id,
|
|
276
|
+
"agent_name": config.agent_name,
|
|
277
|
+
"flock_definition": config.flock_definition,
|
|
278
|
+
"created_at": config.created_at.isoformat(),
|
|
279
|
+
"share_type": config.share_type,
|
|
280
|
+
"chat_message_key": config.chat_message_key,
|
|
281
|
+
"chat_history_key": config.chat_history_key,
|
|
282
|
+
"chat_response_key": config.chat_response_key,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
await table_client.upsert_entity(entity)
|
|
286
|
+
logger.info(f"Saved shared link config to Azure Table Storage for ID: {config.share_id} with type: {config.share_type}")
|
|
287
|
+
return config
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.error(f"Error saving config to Azure Table Storage for ID {config.share_id}: {e}", exc_info=True)
|
|
290
|
+
raise
|
|
291
|
+
|
|
292
|
+
async def get_config(self, share_id: str) -> SharedLinkConfig | None:
|
|
293
|
+
"""Retrieves a shared link configuration from Azure Table Storage by its ID."""
|
|
294
|
+
try:
|
|
295
|
+
table_client = self.table_service_client.get_table_client(self.shared_links_table_name)
|
|
296
|
+
|
|
297
|
+
entity = await table_client.get_entity(partition_key="shared_links", row_key=share_id)
|
|
298
|
+
|
|
299
|
+
logger.debug(f"Retrieved shared link config from Azure Table Storage for ID: {share_id}")
|
|
300
|
+
return SharedLinkConfig(
|
|
301
|
+
share_id=entity["share_id"],
|
|
302
|
+
agent_name=entity["agent_name"],
|
|
303
|
+
created_at=entity["created_at"], # Pydantic will parse from ISO format
|
|
304
|
+
flock_definition=entity["flock_definition"],
|
|
305
|
+
share_type=entity.get("share_type", "agent_run"),
|
|
306
|
+
chat_message_key=entity.get("chat_message_key"),
|
|
307
|
+
chat_history_key=entity.get("chat_history_key"),
|
|
308
|
+
chat_response_key=entity.get("chat_response_key"),
|
|
309
|
+
)
|
|
310
|
+
except ResourceNotFoundError:
|
|
311
|
+
logger.debug(f"No shared link config found in Azure Table Storage for ID: {share_id}")
|
|
312
|
+
return None
|
|
313
|
+
except Exception as e:
|
|
314
|
+
logger.error(f"Error retrieving config from Azure Table Storage for ID {share_id}: {e}", exc_info=True)
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
async def delete_config(self, share_id: str) -> bool:
|
|
318
|
+
"""Deletes a shared link configuration from Azure Table Storage by its ID."""
|
|
319
|
+
try:
|
|
320
|
+
table_client = self.table_service_client.get_table_client(self.shared_links_table_name)
|
|
321
|
+
|
|
322
|
+
await table_client.delete_entity(partition_key="shared_links", row_key=share_id)
|
|
323
|
+
logger.info(f"Deleted shared link config from Azure Table Storage for ID: {share_id}")
|
|
324
|
+
return True
|
|
325
|
+
except ResourceNotFoundError:
|
|
326
|
+
logger.info(f"Attempted to delete non-existent shared link config from Azure Table Storage for ID: {share_id}")
|
|
327
|
+
return False
|
|
328
|
+
except Exception as e:
|
|
329
|
+
logger.error(f"Error deleting config from Azure Table Storage for ID {share_id}: {e}", exc_info=True)
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
# ----------------------- Feedback methods -----------------------
|
|
333
|
+
|
|
334
|
+
async def save_feedback(self, record: FeedbackRecord) -> FeedbackRecord:
|
|
335
|
+
"""Persist a feedback record to Azure Table Storage."""
|
|
336
|
+
try:
|
|
337
|
+
table_client = self.table_service_client.get_table_client(self.feedback_table_name)
|
|
338
|
+
|
|
339
|
+
entity = {
|
|
340
|
+
"PartitionKey": "feedback", # Use a fixed partition key for simplicity
|
|
341
|
+
"RowKey": record.feedback_id,
|
|
342
|
+
"feedback_id": record.feedback_id,
|
|
343
|
+
"share_id": record.share_id,
|
|
344
|
+
"context_type": record.context_type,
|
|
345
|
+
"reason": record.reason,
|
|
346
|
+
"expected_response": record.expected_response,
|
|
347
|
+
"actual_response": record.actual_response,
|
|
348
|
+
"flock_name": record.flock_name,
|
|
349
|
+
"agent_name": record.agent_name,
|
|
350
|
+
"flock_definition": record.flock_definition,
|
|
351
|
+
"created_at": record.created_at.isoformat(),
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
await table_client.upsert_entity(entity)
|
|
355
|
+
logger.info(f"Saved feedback to Azure Table Storage: {record.feedback_id} (share={record.share_id})")
|
|
356
|
+
return record
|
|
357
|
+
except Exception as e:
|
|
358
|
+
logger.error(f"Error saving feedback to Azure Table Storage {record.feedback_id}: {e}", exc_info=True)
|
|
359
|
+
raise
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# ----------------------- Factory Function -----------------------
|
|
363
|
+
|
|
364
|
+
def create_shared_link_store(store_type: str | None = None, connection_string: str | None = None) -> SharedLinkStoreInterface:
|
|
365
|
+
"""Factory function to create the appropriate shared link store based on configuration.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
store_type: Type of store to create ("local" for SQLite, "azure-storage" for Azure Table Storage)
|
|
369
|
+
connection_string: Connection string for the store (file path for SQLite, connection string for Azure)
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Configured SharedLinkStoreInterface implementation
|
|
373
|
+
"""
|
|
374
|
+
import os
|
|
375
|
+
|
|
376
|
+
# Get values from environment if not provided
|
|
377
|
+
if store_type is None:
|
|
378
|
+
store_type = os.getenv("FLOCK_WEBAPP_STORE", "local").lower()
|
|
379
|
+
|
|
380
|
+
if connection_string is None:
|
|
381
|
+
connection_string = os.getenv("FLOCK_WEBAPP_STORE_CONNECTION", ".flock/shared_links.db")
|
|
382
|
+
|
|
383
|
+
if store_type == "local":
|
|
384
|
+
return SQLiteSharedLinkStore(connection_string)
|
|
385
|
+
elif store_type == "azure-storage":
|
|
386
|
+
return AzureTableSharedLinkStore(connection_string)
|
|
387
|
+
else:
|
|
388
|
+
raise ValueError(f"Unsupported store type: {store_type}. Supported types: 'local', 'azure-storage'")
|
flock/webapp/run.py
CHANGED
|
@@ -142,7 +142,8 @@ def start_unified_server(
|
|
|
142
142
|
"flock.webapp.app.main:app",
|
|
143
143
|
host=host,
|
|
144
144
|
port=port,
|
|
145
|
-
reload=False # Critical for programmatically set state like flock_instance
|
|
145
|
+
reload=False, # Critical for programmatically set state like flock_instance
|
|
146
|
+
# root_path=os.getenv("FLOCK_ROOT_PATH", "")
|
|
146
147
|
)
|
|
147
148
|
|
|
148
149
|
except ImportError as e:
|
|
@@ -206,6 +207,7 @@ def main():
|
|
|
206
207
|
host=host,
|
|
207
208
|
port=port,
|
|
208
209
|
reload=webapp_reload,
|
|
210
|
+
# root_path=os.getenv("FLOCK_ROOT_PATH", "")
|
|
209
211
|
)
|
|
210
212
|
|
|
211
213
|
|
flock/webapp/templates/base.html
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
<title>{% block title %}Flock UI{% endblock %}</title>
|
|
8
8
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" />
|
|
9
9
|
<!-- Split modular CSS files -->
|
|
10
|
-
<link rel="stylesheet"
|
|
11
|
-
|
|
12
|
-
<link rel="stylesheet"
|
|
13
|
-
|
|
14
|
-
<link rel="stylesheet" href="
|
|
15
|
-
<link rel="stylesheet" href="
|
|
10
|
+
<link rel="stylesheet"
|
|
11
|
+
href="{{ url_for('static', path='css/layout.css') }}">
|
|
12
|
+
<link rel="stylesheet"
|
|
13
|
+
href="{{ url_for('static', path='css/header.css') }}">
|
|
14
|
+
<link rel="stylesheet" href="{{ url_for('static', path='css/sidebar.css') }}">
|
|
15
|
+
<link rel="stylesheet" href="{{ url_for('static', path='css/components.css') }}">
|
|
16
|
+
<link rel="stylesheet" href="{{ url_for('static', path='css/chat.css') }}">
|
|
17
|
+
<link rel="stylesheet" href="{{ url_for('static', path='css/two-pane.css') }}">
|
|
16
18
|
<!-- Font Awesome for icons -->
|
|
17
19
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
|
18
20
|
<!-- Prism.js CSS for syntax highlighting (okaidia theme) -->
|
|
@@ -42,14 +44,11 @@
|
|
|
42
44
|
</div>
|
|
43
45
|
|
|
44
46
|
<header class="top-header">
|
|
45
|
-
<span><strong>🐧 Flock Playground 🐤</strong></span>
|
|
46
|
-
<span id="header-flock-status-container" hx-get="/ui/htmx/header-flock-status?ui_mode={{ ui_mode }}"
|
|
47
|
+
<span><strong>🐧 Flock Playground 🐤</strong></span> <span id="header-flock-status-container" hx-get="{{ url_for('htmx_get_header_flock_status') }}?ui_mode={{ ui_mode }}"
|
|
47
48
|
hx-trigger="load, flockLoaded from:body, flockCleared from:body" hx-swap="innerHTML">
|
|
48
49
|
<small>Loading status...</small> {# Placeholder while loading #}
|
|
49
50
|
</span>
|
|
50
|
-
</header>
|
|
51
|
-
|
|
52
|
-
<aside class="sidebar" hx-get="/ui/htmx/sidebar?ui_mode={{ ui_mode }}"
|
|
51
|
+
</header> <aside class="sidebar" hx-get="{{ url_for('htmx_get_sidebar') }}?ui_mode={{ ui_mode }}"
|
|
53
52
|
hx-trigger="load, flockLoaded from:body, flockListChanged from:body" hx-swap="innerHTML">
|
|
54
53
|
<p>Loading navigation...</p><progress></progress>
|
|
55
54
|
</aside>
|
flock/webapp/templates/chat.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Flock Chat</title>
|
|
7
7
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" />
|
|
8
|
-
<link rel="stylesheet" href="
|
|
8
|
+
<link rel="stylesheet" href="{{ url_for('static', path='css/chat.css') }}">
|
|
9
9
|
{# Prism.js CSS for syntax highlighting (okaidia theme) #}
|
|
10
10
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css" referrerpolicy="no-referrer" />
|
|
11
11
|
{# Inject active theme variables #}
|
|
@@ -34,25 +34,22 @@
|
|
|
34
34
|
<hgroup>
|
|
35
35
|
<h2>Flock Chat</h2>
|
|
36
36
|
<h3>{{ chat_subtitle }}</h3>
|
|
37
|
-
</hgroup>
|
|
38
|
-
{
|
|
39
|
-
<button class="secondary outline" hx-get="/chat/settings-standalone" hx-target="#chat-content-area" hx-swap="innerHTML" style="min-width:auto;">Settings</button>
|
|
37
|
+
</hgroup> {% if not is_shared_chat %}
|
|
38
|
+
<button class="secondary outline" hx-get="{{ url_for('chat_settings_standalone') }}" hx-target="#chat-content-area" hx-swap="innerHTML" style="min-width:auto;">Settings</button>
|
|
40
39
|
{% else %}
|
|
41
40
|
{# Optionally, could show a disabled settings button or some other indicator #}
|
|
42
41
|
<span style="font-size: 0.8rem; color: var(--pico-muted-color);">Settings frozen for shared session.</span>
|
|
43
42
|
{% endif %}
|
|
44
43
|
</div>
|
|
45
44
|
|
|
46
|
-
<div id="chat-content-area">
|
|
47
|
-
|
|
48
|
-
hx-get="{% if is_shared_chat %}/chat/messages-shared/{{ share_id }}{% else %}/chat/messages{% endif %}"
|
|
45
|
+
<div id="chat-content-area"> <div id="chat-log"
|
|
46
|
+
hx-get="{% if is_shared_chat %}{{ url_for('chat_history_shared_partial', share_id=share_id) }}{% else %}{{ url_for('chat_history_partial') }}{% endif %}"
|
|
49
47
|
hx-trigger="load" {# Polling removed #}
|
|
50
48
|
hx-swap="innerHTML">
|
|
51
49
|
<p><em>Loading chat…</em></p>
|
|
52
50
|
</div>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
hx-post="{% if is_shared_chat %}/chat/send-shared{% else %}/chat/send{% endif %}"
|
|
51
|
+
<form id="chat-form-standalone"
|
|
52
|
+
hx-post="{% if is_shared_chat %}{{ url_for('chat_send_shared') }}{% else %}{{ url_for('chat_send') }}{% endif %}"
|
|
56
53
|
hx-target="#chat-log"
|
|
57
54
|
hx-swap="innerHTML"
|
|
58
55
|
hx-disabled-elt="input[name='message'], button[type='submit']"
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Chat Settings</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" />
|
|
8
|
-
<link rel="stylesheet" href="
|
|
9
|
-
<link rel="stylesheet" href="/static/css/header.css">
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" /> <link rel="stylesheet" href="{{ url_for('static', path='css/chat.css') }}">
|
|
8
|
+
<link rel="stylesheet" href="{{ url_for('static', path='css/header.css') }}">
|
|
10
9
|
{# Active theme CSS #}
|
|
11
10
|
{% if theme_css %}<style>{{ theme_css | safe }}</style>{% endif %}
|
|
12
11
|
<script src="https://unpkg.com/htmx.org@1.9.10" crossorigin="anonymous"></script>
|
|
@@ -14,7 +13,7 @@
|
|
|
14
13
|
<body class="chat-page">
|
|
15
14
|
<main style="max-width: 700px; margin: 2rem auto;">
|
|
16
15
|
{% include 'partials/_chat_settings_form.html' %}
|
|
17
|
-
<p style="text-align:center; margin-top: 1rem;"><a href="
|
|
16
|
+
<p style="text-align:center; margin-top: 1rem;"><a href="{{ url_for('chat_page') }}">← Back to Chat</a></p>
|
|
18
17
|
</main>
|
|
19
18
|
</body>
|
|
20
19
|
</html>
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
{% block content %}
|
|
6
6
|
{# Main content area is now loaded dynamically based on sidebar clicks #}
|
|
7
7
|
{# We can load the properties form by default when this page is hit #}
|
|
8
|
-
<div id="editor-main-content"
|
|
9
|
-
hx-get="/ui/api/flock/htmx/flock-properties-form"
|
|
8
|
+
<div id="editor-main-content" hx-get="{{ url_for('htmx_get_flock_properties_form') }}"
|
|
10
9
|
hx-trigger="load"
|
|
11
10
|
hx-swap="innerHTML">
|
|
12
11
|
<article>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
{% block content %}
|
|
6
6
|
{# This content will be loaded dynamically via HTMX from the sidebar #}
|
|
7
7
|
{# We can load the 'load/manage' view by default #}
|
|
8
|
-
<div id="dashboard-content" hx-get="
|
|
8
|
+
<div id="dashboard-content" hx-get="{{ url_for('htmx_get_load_flock_view') }}" hx-trigger="load" hx-swap="innerHTML">
|
|
9
9
|
<p>Loading dashboard content...</p>
|
|
10
10
|
<progress></progress>
|
|
11
11
|
</div>
|
|
@@ -11,12 +11,9 @@
|
|
|
11
11
|
x-transition:leave-end="opacity-0">
|
|
12
12
|
{{ form_message }}
|
|
13
13
|
</div>
|
|
14
|
-
{% endif %}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
hx-post="/ui/api/flock/htmx/agents"
|
|
18
|
-
{% else %}
|
|
19
|
-
hx-put="/ui/api/flock/htmx/agents/{{ agent.name if agent else '' }}"
|
|
14
|
+
{% endif %} <form {% if is_new %} hx-post="{{ url_for('htmx_create_agent') }}"
|
|
15
|
+
{% else %}
|
|
16
|
+
hx-put="{{ url_for('htmx_update_agent', original_agent_name=agent.name if agent else '') }}"
|
|
20
17
|
{% endif %}
|
|
21
18
|
hx-target="#agent-detail-form-content"
|
|
22
19
|
hx-swap="innerHTML"
|
|
@@ -75,18 +72,15 @@
|
|
|
75
72
|
<button type="submit">
|
|
76
73
|
{% if is_new %}Create Agent{% else %}Save Changes{% endif %}
|
|
77
74
|
</button>
|
|
78
|
-
{% if not is_new and agent %}
|
|
79
|
-
|
|
80
|
-
hx-delete="/ui/api/flock/htmx/agents/{{ agent.name }}"
|
|
81
|
-
hx-target="#agent-detail-form-content"
|
|
75
|
+
{% if not is_new and agent %} <button type="button" role="button" class="secondary outline" hx-delete="{{ url_for('htmx_delete_agent', agent_name=agent.name) }}"
|
|
76
|
+
hx-target="#agent-detail-form-content"
|
|
82
77
|
hx-confirm="Are you sure you want to delete agent '{{ agent.name }}'?"
|
|
83
78
|
hx-indicator="#agent-detail-loading-indicator">
|
|
84
79
|
Delete Agent
|
|
85
80
|
</button>
|
|
86
81
|
{% endif %}
|
|
87
|
-
<button type="button" class="outline"
|
|
88
|
-
hx-
|
|
89
|
-
hx-target="#agent-detail-panel"
|
|
82
|
+
<button type="button" class="outline" hx-get="{{ url_for('htmx_get_new_agent_form') }}"
|
|
83
|
+
hx-target="#agent-detail-panel"
|
|
90
84
|
hx-swap="innerHTML"
|
|
91
85
|
hx-indicator="#agent-detail-loading-indicator">
|
|
92
86
|
Cancel / New Form
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
{% endif %}
|
|
6
6
|
{% if flock.agents %}
|
|
7
7
|
<ul class="item-list">
|
|
8
|
-
{% for agent_name, agent in flock.agents.items() %}
|
|
9
|
-
<li hx-get="/ui/api/flock/htmx/agents/{{ agent.name }}/details-form" hx-target="#agent-detail-panel" hx-swap="innerHTML" hx-indicator="#agent-detail-loading" onclick="this.closest('ul').querySelectorAll('li').forEach(li => li.classList.remove('selected-item')); this.classList.add('selected-item');">
|
|
8
|
+
{% for agent_name, agent in flock.agents.items() %} <li hx-get="{{ url_for('htmx_get_agent_details_form', agent_name=agent.name) }}" hx-target="#agent-detail-panel" hx-swap="innerHTML" hx-indicator="#agent-detail-loading" onclick="this.closest('ul').querySelectorAll('li').forEach(li => li.classList.remove('selected-item')); this.classList.add('selected-item');">
|
|
10
9
|
<strong>{{ agent.name }}</strong><br>
|
|
11
10
|
<small>{{ agent.resolved_description|truncate(80) if agent.resolved_description else 'No description' }}</small>
|
|
12
11
|
</li>
|
|
@@ -20,14 +20,13 @@
|
|
|
20
20
|
<header class="grid">
|
|
21
21
|
<h2>Agents ({{ flock.agents|length }}) </h2>
|
|
22
22
|
<div style="text-align: right;">
|
|
23
|
-
<button role="button" class="outline" hx-get="
|
|
23
|
+
<button role="button" class="outline" hx-get="{{ url_for('htmx_get_new_agent_form') }}" hx-target="#agent-detail-panel" hx-swap="innerHTML">Add New Agent</button>
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
26
|
</header>
|
|
27
27
|
|
|
28
28
|
{# Content of the left pane now goes into this article #}
|
|
29
|
-
<div style="padding: var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal); flex-grow: 1; display: flex; flex-direction: column;">
|
|
30
|
-
<div hx-get="/ui/api/flock/htmx/agent-list"
|
|
29
|
+
<div style="padding: var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal); flex-grow: 1; display: flex; flex-direction: column;"> <div hx-get="{{ url_for('htmx_get_agent_list') }}"
|
|
31
30
|
hx-trigger="load, agentListChanged from:body"
|
|
32
31
|
hx-swap="innerHTML"
|
|
33
32
|
id="agent-list-panel"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<article id="chat-container" class="chat-container">
|
|
2
|
-
<div id="chat-log" hx-get="
|
|
2
|
+
<div id="chat-log" hx-get="{{ url_for('chat_history_partial') }}" hx-trigger="load" hx-swap="innerHTML">
|
|
3
3
|
<p><em>Loading chat…</em></p>
|
|
4
4
|
</div>
|
|
5
5
|
<form id="chat-form-embedded" class="chat-form"
|
|
6
|
-
hx-post="
|
|
6
|
+
hx-post="{{ url_for('chat_send') }}"
|
|
7
7
|
hx-target="#chat-log"
|
|
8
8
|
hx-swap="innerHTML"
|
|
9
9
|
hx-disabled-elt="input[name='message'], button[type='submit']"
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
<form class="chat-settings-form" hx-post="
|
|
1
|
+
<form class="chat-settings-form" hx-post="{{ url_for('chat_settings_submit') }}" hx-target="this" hx-swap="outerHTML" hx-indicator="#chat-settings-saving" style="max-width: 500px; margin: 1rem auto;">
|
|
2
2
|
<hgroup style="margin-bottom: 1rem;">
|
|
3
3
|
<h3>Chat Settings</h3>
|
|
4
4
|
<h4>Select agent & field mappings</h4>
|
|
5
5
|
</hgroup>
|
|
6
6
|
|
|
7
|
-
<label for="agent_name_field">Chat Agent</label>
|
|
8
|
-
|
|
9
|
-
hx-get="/chat/htmx/settings-form"
|
|
7
|
+
<label for="agent_name_field">Chat Agent</label> <select id="agent_name_field" name="agent_name"
|
|
8
|
+
hx-get="{{ url_for('htmx_chat_settings_partial') }}"
|
|
10
9
|
hx-target="closest form"
|
|
11
10
|
hx-trigger="change"
|
|
12
11
|
hx-include="[name=agent_name]">
|
|
@@ -57,9 +56,8 @@
|
|
|
57
56
|
{% endif %}
|
|
58
57
|
|
|
59
58
|
<div class="grid" style="margin-top: 1rem;">
|
|
60
|
-
<button type="submit">Save Settings</button>
|
|
61
|
-
<button type="
|
|
62
|
-
<button type="button" class="secondary outline" hx-get="/chat/htmx/chat-view" hx-target="#chat-content-area" hx-swap="innerHTML">Cancel</button>
|
|
59
|
+
<button type="submit">Save Settings</button> <button type="reset" class="outline">Reset</button>
|
|
60
|
+
<button type="button" class="secondary outline" hx-get="{{ url_for('htmx_chat_view') }}" hx-target="#chat-content-area" hx-swap="innerHTML">Cancel</button>
|
|
63
61
|
</div>
|
|
64
62
|
<div id="chat-settings-saving" class="htmx-indicator" style="text-align:center; margin-top:0.5rem;"><progress indeterminate></progress></div>
|
|
65
63
|
|
|
@@ -69,7 +67,7 @@
|
|
|
69
67
|
<p><small>Create a shareable link for this chat configuration. The current Flock and these chat settings will be frozen for the shared session.</small></p>
|
|
70
68
|
<a href="#"
|
|
71
69
|
id="shareChatHtmxLink"
|
|
72
|
-
hx-post="
|
|
70
|
+
hx-post="{{ url_for('htmx_generate_share_link') }}"
|
|
73
71
|
hx-target="#shareChatLinkDisplayArea"
|
|
74
72
|
hx-swap="innerHTML"
|
|
75
73
|
hx-indicator="#share-chat-loading-indicator"
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
{% endif %}
|
|
16
16
|
|
|
17
17
|
<section id="create-by-details-section">
|
|
18
|
-
<h4>Define New Flock Properties</h4>
|
|
19
|
-
<form hx-post="/ui/create-flock"
|
|
18
|
+
<h4>Define New Flock Properties</h4> <form hx-post="{{ url_for('create_flock') }}"
|
|
20
19
|
hx-target="#main-content-area" {# On success, loads properties form into main area #}
|
|
21
20
|
hx-swap="innerHTML"
|
|
22
21
|
hx-indicator="#create-indicator-details">
|
|
@@ -37,8 +36,7 @@
|
|
|
37
36
|
<hr>
|
|
38
37
|
|
|
39
38
|
<section id="create-by-upload-section">
|
|
40
|
-
<h4>Upload Existing Flock File</h4>
|
|
41
|
-
<form hx-post="/ui/load-flock-action/by-upload"
|
|
39
|
+
<h4>Upload Existing Flock File</h4> <form hx-post="{{ url_for('load_flock_by_upload') }}"
|
|
42
40
|
hx-target="#main-content-area" {# On success, loads properties form into main area #}
|
|
43
41
|
hx-swap="innerHTML"
|
|
44
42
|
hx-encoding="multipart/form-data"
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
<header>
|
|
3
3
|
<h5>Flock File: <code>{{ selected_filename }}</code></h5>
|
|
4
4
|
</header>
|
|
5
|
-
<p>Details for this Flock file (e.g., size, last modified) could be shown here if fetched.</p>
|
|
6
|
-
<form hx-post="/ui/load-flock-action/by-name"
|
|
5
|
+
<p>Details for this Flock file (e.g., size, last modified) could be shown here if fetched.</p> <form hx-post="{{ url_for('ui_load_flock_by_name_action') }}"
|
|
7
6
|
hx-target="#main-content-area" {# Target the main area to show editor on success #}
|
|
8
7
|
hx-swap="innerHTML"
|
|
9
8
|
hx-indicator="#dashboard-flock-detail-loading-indicator">
|
|
@@ -14,5 +13,5 @@
|
|
|
14
13
|
<hr>
|
|
15
14
|
<p><small>Or, you can use the form below to upload a different Flock file.</small></p>
|
|
16
15
|
{# Include the upload form again or link back to it #}
|
|
17
|
-
<div hx-get="
|
|
16
|
+
<div hx-get="{{ url_for('htmx_get_dashboard_upload_flock_form') }}" hx-trigger="load" hx-swap="innerHTML"></div>
|
|
18
17
|
</article>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{% if flock_files %}
|
|
2
2
|
<ul class="item-list">
|
|
3
|
-
{% for file_name in flock_files %}
|
|
4
|
-
<li hx-get="/ui/htmx/dashboard-flock-properties-preview/{{ file_name }}"
|
|
3
|
+
{% for file_name in flock_files %} <li hx-get="{{ url_for('htmx_get_dashboard_flock_properties_preview', filename=file_name) }}"
|
|
5
4
|
hx-target="#flock-properties-or-action-content" {# Target the right pane #}
|
|
6
5
|
hx-swap="innerHTML"
|
|
7
6
|
hx-indicator="#flock-properties-or-action-content progress"
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
<p><strong>Default Model:</strong> {{ preview_flock.model or 'Not set' }}</p>
|
|
7
7
|
<p><strong>Description:</strong> {{ preview_flock.description or 'None' }}</p>
|
|
8
8
|
<p><strong>Agents:</strong> {{ preview_flock.agents_count }}</p>
|
|
9
|
-
|
|
10
|
-
<form hx-post="/ui/load-flock-action/by-name"
|
|
9
|
+
<form hx-post="{{ url_for('ui_load_flock_by_name_action') }}"
|
|
11
10
|
hx-target="#main-content-area" {# This will load the full editor for this flock #}
|
|
12
11
|
hx-swap="innerHTML"
|
|
13
12
|
hx-indicator="#flock-preview-loading-indicator">
|
|
@@ -18,7 +17,7 @@
|
|
|
18
17
|
{% else %}
|
|
19
18
|
<p class="error">Could not load preview for <code>{{ selected_filename }}</code>. It might be corrupted or not a valid Flock file.</p>
|
|
20
19
|
<p><small>You can still attempt to load it into the editor.</small></p>
|
|
21
|
-
<form hx-post="
|
|
20
|
+
<form hx-post="{{ url_for('ui_load_flock_by_name_action') }}" hx-target="#main-content-area" hx-swap="innerHTML">
|
|
22
21
|
<input type="hidden" name="selected_flock_filename" value="{{ selected_filename }}">
|
|
23
22
|
<button type="submit" class="secondary">Attempt to Load into Editor</button>
|
|
24
23
|
</form>
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<article>
|
|
2
2
|
<header>
|
|
3
3
|
<h5>Upload New Flock File</h5>
|
|
4
|
-
</header>
|
|
5
|
-
<form hx-post="/ui/load-flock-action/by-upload"
|
|
4
|
+
</header> <form hx-post="{{ url_for('load_flock_by_upload') }}"
|
|
6
5
|
hx-target="#main-content-area" {# Target the main area to show editor on success #}
|
|
7
6
|
hx-swap="innerHTML"
|
|
8
7
|
hx-encoding="multipart/form-data"
|
|
@@ -11,11 +11,9 @@
|
|
|
11
11
|
{% for var in env_vars %}
|
|
12
12
|
<tr>
|
|
13
13
|
<td>{{ var.name }}</td>
|
|
14
|
-
<td>{{ var.value }}</td>
|
|
15
|
-
<td>
|
|
16
|
-
<button class="secondary small outline" hx-post="/ui/htmx/env-edit" hx-vals='{"var_name": "{{ var.name }}"}'
|
|
14
|
+
<td>{{ var.value }}</td> <td> <button class="secondary small outline" hx-post="{{ url_for('htmx_env_edit') }}" hx-vals='{"var_name": "{{ var.name }}"}'
|
|
17
15
|
hx-prompt="Enter new value for {{ var.name }}" hx-target="#env-vars-container" hx-swap="outerHTML">Edit</button>
|
|
18
|
-
<button class="contrast small outline" hx-post="
|
|
16
|
+
<button class="contrast small outline" hx-post="{{ url_for('htmx_env_delete') }}" hx-vals='{"var_name": "{{ var.name }}"}'
|
|
19
17
|
hx-confirm="Delete {{ var.name }}?" hx-target="#env-vars-container" hx-swap="outerHTML" style="margin-left:0.25rem;">Delete</button>
|
|
20
18
|
</td>
|
|
21
19
|
</tr>
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// Create URL template for agent input forms
|
|
3
|
+
window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agent_name="AGENT_PLACEHOLDER") }}';
|
|
4
|
+
</script>
|
|
5
|
+
|
|
1
6
|
<article id="execution-form-content">
|
|
2
7
|
<header>
|
|
3
8
|
<h2>Run Flock</h2>
|
|
4
9
|
</header>
|
|
5
|
-
{% if flock and flock.agents %}
|
|
6
|
-
|
|
7
|
-
hx-target="#results-display"
|
|
10
|
+
{% if flock and flock.agents %} <form hx-post="{{ url_for('htmx_run_flock') }}"
|
|
11
|
+
hx-target="#results-display"
|
|
8
12
|
hx-swap="innerHTML"
|
|
9
13
|
hx-indicator="#run-loading-indicator"
|
|
10
14
|
x-data="{ selectedAgentForInput: '' }">
|
|
@@ -12,11 +16,10 @@
|
|
|
12
16
|
<label for="start_agent_name_select">Select Start Agent:</label>
|
|
13
17
|
<select id="start_agent_name_select"
|
|
14
18
|
name="start_agent_name"
|
|
15
|
-
required
|
|
16
|
-
x-model="selectedAgentForInput"
|
|
17
|
-
@change="
|
|
19
|
+
required x-model="selectedAgentForInput" @change="
|
|
18
20
|
if ($event.target.value) {
|
|
19
|
-
|
|
21
|
+
const url = window.agentInputFormUrlTemplate.replace('AGENT_PLACEHOLDER', $event.target.value);
|
|
22
|
+
htmx.ajax('GET', url, {target: '#dynamic-input-form-fields', swap: 'innerHTML', indicator: '#input-form-loading-indicator'});
|
|
20
23
|
} else {
|
|
21
24
|
document.getElementById('dynamic-input-form-fields').innerHTML = '<p><small>Select an agent to see its input fields.</small></p>';
|
|
22
25
|
}
|
|
@@ -37,10 +40,9 @@
|
|
|
37
40
|
|
|
38
41
|
<button type="submit" {% if not flock.agents %}disabled{% endif %}>Run Flock</button>
|
|
39
42
|
|
|
40
|
-
<div id="share-agent-link-container" style="margin-top: 0.5rem;">
|
|
41
|
-
<a href="#"
|
|
43
|
+
<div id="share-agent-link-container" style="margin-top: 0.5rem;"> <a href="#"
|
|
42
44
|
id="shareAgentHtmxLink"
|
|
43
|
-
hx-post="
|
|
45
|
+
hx-post="{{ url_for('htmx_generate_share_link') }}"
|
|
44
46
|
hx-target="#shareLinkDisplayArea"
|
|
45
47
|
hx-swap="innerHTML"
|
|
46
48
|
hx-indicator="#share-loading-indicator"
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
<div class="pane-header" style="display: flex; justify-content: flex-end; margin-bottom: 10px;">
|
|
10
10
|
<a href="#" class="pane-toggle" @click.prevent="showLeftPane = false" aria-label="Hide left pane">Hide</a>
|
|
11
11
|
</div>
|
|
12
|
-
{# The Execution Form will be loaded here #}
|
|
13
|
-
|
|
14
|
-
form #} hx-trigger="load" hx-swap="innerHTML">
|
|
12
|
+
{# The Execution Form will be loaded here #} <div id="execution-form-wrapper" hx-get="{{ url_for('htmx_get_execution_form_content') }}"
|
|
13
|
+
hx-trigger="load" hx-swap="innerHTML">
|
|
15
14
|
<p>Loading execution form...</p><progress indeterminate></progress>
|
|
16
15
|
</div>
|
|
17
16
|
</section>
|