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.

Files changed (56) hide show
  1. flock/core/config/flock_agent_config.py +11 -0
  2. flock/core/config/scheduled_agent_config.py +40 -0
  3. flock/core/flock_agent.py +7 -1
  4. flock/core/flock_factory.py +129 -2
  5. flock/core/flock_scheduler.py +166 -0
  6. flock/core/logging/logging.py +8 -0
  7. flock/core/mcp/flock_mcp_server.py +30 -4
  8. flock/core/mcp/flock_mcp_tool_base.py +1 -1
  9. flock/core/mcp/mcp_client.py +57 -28
  10. flock/core/mcp/mcp_client_manager.py +1 -1
  11. flock/core/mcp/mcp_config.py +245 -9
  12. flock/core/mcp/types/callbacks.py +3 -5
  13. flock/core/mcp/types/factories.py +12 -14
  14. flock/core/mcp/types/handlers.py +9 -12
  15. flock/core/mcp/types/types.py +205 -2
  16. flock/mcp/servers/sse/flock_sse_server.py +21 -14
  17. flock/mcp/servers/streamable_http/__init__.py +0 -0
  18. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +169 -0
  19. flock/mcp/servers/websockets/flock_websocket_server.py +3 -3
  20. flock/webapp/app/main.py +66 -11
  21. flock/webapp/app/services/sharing_store.py +173 -0
  22. flock/webapp/run.py +3 -1
  23. flock/webapp/templates/base.html +10 -11
  24. flock/webapp/templates/chat.html +7 -10
  25. flock/webapp/templates/chat_settings.html +3 -4
  26. flock/webapp/templates/flock_editor.html +1 -2
  27. flock/webapp/templates/index.html +1 -1
  28. flock/webapp/templates/partials/_agent_detail_form.html +7 -13
  29. flock/webapp/templates/partials/_agent_list.html +1 -2
  30. flock/webapp/templates/partials/_agent_manager_view.html +2 -3
  31. flock/webapp/templates/partials/_chat_container.html +2 -2
  32. flock/webapp/templates/partials/_chat_settings_form.html +6 -8
  33. flock/webapp/templates/partials/_create_flock_form.html +2 -4
  34. flock/webapp/templates/partials/_dashboard_flock_detail.html +2 -3
  35. flock/webapp/templates/partials/_dashboard_flock_file_list.html +1 -2
  36. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +2 -3
  37. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +1 -2
  38. flock/webapp/templates/partials/_env_vars_table.html +2 -4
  39. flock/webapp/templates/partials/_execution_form.html +12 -10
  40. flock/webapp/templates/partials/_execution_view_container.html +2 -3
  41. flock/webapp/templates/partials/_flock_file_list.html +2 -3
  42. flock/webapp/templates/partials/_flock_properties_form.html +2 -2
  43. flock/webapp/templates/partials/_flock_upload_form.html +1 -2
  44. flock/webapp/templates/partials/_load_manager_view.html +2 -3
  45. flock/webapp/templates/partials/_registry_viewer_content.html +4 -5
  46. flock/webapp/templates/partials/_settings_env_content.html +2 -3
  47. flock/webapp/templates/partials/_settings_theme_content.html +2 -2
  48. flock/webapp/templates/partials/_settings_view.html +2 -2
  49. flock/webapp/templates/partials/_sidebar.html +27 -39
  50. flock/webapp/templates/registry_viewer.html +7 -10
  51. flock/webapp/templates/shared_run_page.html +7 -10
  52. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/METADATA +3 -1
  53. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/RECORD +56 -51
  54. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/WHEEL +0 -0
  55. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/entry_points.txt +0 -0
  56. {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
 
@@ -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" href="/static/css/layout.css">
11
- <link rel="stylesheet" href="/static/css/header.css">
12
- <link rel="stylesheet" href="/static/css/sidebar.css">
13
- <link rel="stylesheet" href="/static/css/components.css">
14
- <link rel="stylesheet" href="/static/css/chat.css">
15
- <link rel="stylesheet" href="/static/css/two-pane.css">
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>
@@ -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="/static/css/chat.css">
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
- {% if not is_shared_chat %}
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
- <div id="chat-log"
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
- <form id="chat-form-standalone"
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="/static/css/chat.css">
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="/chat">← Back to Chat</a></p>
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="/ui/htmx/load-flock-view" hx-trigger="load" hx-swap="innerHTML">
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
- <form {% if is_new %}
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
- <button type="button" role="button" class="secondary outline"
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-get="/ui/api/flock/htmx/agents/new-agent-form"
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="/ui/api/flock/htmx/agents/new-agent-form" hx-target="#agent-detail-panel" hx-swap="innerHTML">Add New Agent</button>
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="/chat/messages" hx-trigger="load" hx-swap="innerHTML">
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="/chat/send"
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="/chat/settings" hx-target="this" hx-swap="outerHTML" hx-indicator="#chat-settings-saving" style="max-width: 500px; margin: 1rem auto;">
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
- <select id="agent_name_field" name="agent_name"
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="reset" class="outline">Reset</button>
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="/ui/htmx/share/chat/generate-link"
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="/ui/htmx/dashboard-upload-flock-form" hx-trigger="load" hx-swap="innerHTML"></div>
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="/ui/load-flock-action/by-name" hx-target="#main-content-area" hx-swap="innerHTML">
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="/ui/htmx/env-delete" hx-vals='{"var_name": "{{ var.name }}"}'
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
- <form hx-post="/ui/api/flock/htmx/run"
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
- htmx.ajax('GET', '/ui/api/flock/htmx/agents/' + $event.target.value + '/input-form', {target: '#dynamic-input-form-fields', swap: 'innerHTML', indicator: '#input-form-loading-indicator'});
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="/ui/htmx/share/generate-link"
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
- <div id="execution-form-wrapper" hx-get="/ui/api/flock/htmx/execution-form-content" {# New endpoint for just the
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>