flock-core 0.4.0b46__py3-none-any.whl → 0.4.0b49__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 (50) hide show
  1. flock/__init__.py +45 -3
  2. flock/core/flock.py +105 -61
  3. flock/core/flock_registry.py +45 -38
  4. flock/core/util/spliter.py +4 -0
  5. flock/evaluators/__init__.py +1 -0
  6. flock/evaluators/declarative/__init__.py +1 -0
  7. flock/modules/__init__.py +1 -0
  8. flock/modules/assertion/__init__.py +1 -0
  9. flock/modules/callback/__init__.py +1 -0
  10. flock/modules/mem0/__init__.py +1 -0
  11. flock/modules/mem0/mem0_module.py +63 -0
  12. flock/modules/mem0graph/__init__.py +1 -0
  13. flock/modules/mem0graph/mem0_graph_module.py +63 -0
  14. flock/modules/memory/__init__.py +1 -0
  15. flock/modules/output/__init__.py +1 -0
  16. flock/modules/performance/__init__.py +1 -0
  17. flock/tools/__init__.py +188 -0
  18. flock/{core/tools → tools}/azure_tools.py +284 -0
  19. flock/tools/code_tools.py +56 -0
  20. flock/tools/file_tools.py +140 -0
  21. flock/{core/tools/dev_tools/github.py → tools/github_tools.py} +3 -3
  22. flock/{core/tools → tools}/markdown_tools.py +14 -4
  23. flock/tools/system_tools.py +9 -0
  24. flock/{core/tools/llm_tools.py → tools/text_tools.py} +47 -25
  25. flock/tools/web_tools.py +90 -0
  26. flock/{core/tools → tools}/zendesk_tools.py +6 -6
  27. flock/webapp/app/api/execution.py +130 -30
  28. flock/webapp/app/chat.py +303 -16
  29. flock/webapp/app/config.py +15 -1
  30. flock/webapp/app/dependencies.py +22 -0
  31. flock/webapp/app/main.py +509 -18
  32. flock/webapp/app/services/flock_service.py +38 -13
  33. flock/webapp/app/services/sharing_models.py +43 -0
  34. flock/webapp/app/services/sharing_store.py +156 -0
  35. flock/webapp/static/css/chat.css +57 -0
  36. flock/webapp/templates/chat.html +29 -4
  37. flock/webapp/templates/partials/_chat_messages.html +1 -1
  38. flock/webapp/templates/partials/_chat_settings_form.html +22 -0
  39. flock/webapp/templates/partials/_execution_form.html +28 -1
  40. flock/webapp/templates/partials/_share_chat_link_snippet.html +11 -0
  41. flock/webapp/templates/partials/_share_link_snippet.html +35 -0
  42. flock/webapp/templates/shared_run_page.html +116 -0
  43. flock/workflow/activities.py +1 -0
  44. {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/METADATA +27 -14
  45. {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/RECORD +48 -28
  46. flock/core/tools/basic_tools.py +0 -317
  47. flock/modules/zep/zep_module.py +0 -187
  48. {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/WHEEL +0 -0
  49. {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/entry_points.txt +0 -0
  50. {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,5 @@
1
1
  # src/flock/webapp/app/services/flock_service.py
2
- from typing import TYPE_CHECKING, Optional
2
+ from typing import TYPE_CHECKING, Any, Optional
3
3
 
4
4
  import yaml
5
5
 
@@ -248,25 +248,50 @@ def remove_agent_from_current_flock_service(agent_name: str, app_state: object)
248
248
 
249
249
 
250
250
  async def run_current_flock_service(
251
- start_agent_name: str, inputs: dict, app_state: object
252
- ) -> dict | str:
253
- """Runs the Flock instance from app_state."""
254
- current_flock: Flock | None = getattr(app_state, 'flock_instance', None)
251
+ start_agent_name: str,
252
+ inputs: dict[str, Any],
253
+ app_state: Any,
254
+ ) -> dict[str, Any]:
255
+ """Runs the specified agent from the current flock instance in app_state."""
256
+ logger.info(f"Attempting to run agent: {start_agent_name} using flock from app_state.")
257
+
258
+ current_flock: Flock | None = getattr(app_state, "flock_instance", None)
259
+ run_store: RunStore | None = getattr(app_state, "run_store", None)
260
+
255
261
  if not current_flock:
256
- logger.error("Service: Execution failed, no flock loaded.")
257
- return "Error: No flock loaded."
258
- if not start_agent_name or start_agent_name not in current_flock.agents:
259
- logger.error(f"Service: Execution failed, start agent '{start_agent_name}' not found in flock '{current_flock.name}'.")
260
- return f"Error: Start agent '{start_agent_name}' not found."
262
+ logger.error("Run service: No Flock instance available in app_state.")
263
+ return {"error": "No Flock loaded in the application."}
264
+ if not run_store:
265
+ logger.error("Run service: No RunStore instance available in app_state.")
266
+ # Attempt to initialize a default run_store if missing and this service is critical
267
+ # This might indicate an issue in the application lifecycle setup
268
+ logger.warning("Run service: Initializing a default RunStore as none was found in app_state.")
269
+ run_store = RunStore()
270
+ setattr(app_state, "run_store", run_store)
271
+ # Also update global DI if this is how it's managed elsewhere for consistency,
272
+ # though ideally DI setup handles this more centrally.
273
+ # from flock.webapp.app.dependencies import set_global_flock_services
274
+ # set_global_flock_services(current_flock, run_store)
275
+
276
+
277
+ if start_agent_name not in current_flock.agents:
278
+ logger.error(f"Run service: Agent '{start_agent_name}' not found in current flock '{current_flock.name}'.")
279
+ return {"error": f"Agent '{start_agent_name}' not found."}
280
+
261
281
  try:
262
- logger.info(f"Service: Running flock '{current_flock.name}' starting with agent '{start_agent_name}'.")
282
+ logger.info(f"Executing agent '{start_agent_name}' from flock '{current_flock.name}' using app_state.")
283
+ # Direct execution using the flock from app_state
263
284
  result = await current_flock.run_async(
264
285
  start_agent=start_agent_name, input=inputs, box_result=False
265
286
  )
287
+ # Store run details using the run_store from app_state
288
+ if hasattr(run_store, "add_run_details"): # Check if RunStore has this method
289
+ run_id = result.get("run_id", "unknown_run_id") # Assuming run_async result might contain run_id
290
+ run_store.add_run_details(run_id=run_id, agent_name=start_agent_name, inputs=inputs, outputs=result)
266
291
  return result
267
292
  except Exception as e:
268
- logger.error(f"Service: Error during flock execution for '{current_flock.name}': {e}", exc_info=True)
269
- return f"Error: {e!s}"
293
+ logger.error(f"Run service: Error during agent execution: {e}", exc_info=True)
294
+ return {"error": f"An error occurred: {e}"}
270
295
 
271
296
 
272
297
  def get_registered_items_service(item_type: str) -> list[dict]:
@@ -0,0 +1,43 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class SharedLinkConfig(BaseModel):
7
+ """Configuration for a shared Flock agent execution link or chat session."""
8
+
9
+ share_id: str = Field(..., description="Unique identifier for the shared link.")
10
+ agent_name: str = Field(..., description="The name of the agent being shared (for run) or the chat agent (for chat).")
11
+ flock_definition: str = Field(..., description="The YAML/JSON string definition of the Flock the agent belongs to.")
12
+ created_at: datetime = Field(
13
+ default_factory=datetime.utcnow, description="Timestamp of when the link was created."
14
+ )
15
+ share_type: str = Field(default="agent_run", description="Type of share: 'agent_run' or 'chat'")
16
+
17
+ # Chat-specific settings (only relevant if share_type is 'chat')
18
+ chat_message_key: str | None = Field(None, description="Message key for chat input mapping.")
19
+ chat_history_key: str | None = Field(None, description="History key for chat input mapping.")
20
+ chat_response_key: str | None = Field(None, description="Response key for chat output mapping.")
21
+
22
+ # Placeholder for future enhancement: pre-filled input values
23
+ # input_values: Optional[Dict[str, Any]] = Field(
24
+ # None, description="Optional pre-filled input values for the agent."
25
+ # )
26
+
27
+ model_config = {
28
+ "from_attributes": True,
29
+ "json_schema_extra": {
30
+ "examples": [
31
+ {
32
+ "share_id": "abcdef123456",
33
+ "agent_name": "MyChatAgent",
34
+ "flock_definition": "name: MySharedFlock\nagents:\n MyChatAgent:\n input: 'message: str'\n output: 'response: str'\n # ... rest of flock YAML ...",
35
+ "created_at": "2023-10-26T10:00:00Z",
36
+ "share_type": "chat",
37
+ "chat_message_key": "user_input",
38
+ "chat_history_key": "conversation_history",
39
+ "chat_response_key": "agent_output"
40
+ }
41
+ ]
42
+ }
43
+ }
@@ -0,0 +1,156 @@
1
+ import logging
2
+ import sqlite3
3
+ from abc import ABC, abstractmethod
4
+ from pathlib import Path
5
+
6
+ import aiosqlite
7
+
8
+ from flock.webapp.app.services.sharing_models import SharedLinkConfig
9
+
10
+ # Get a logger instance
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class SharedLinkStoreInterface(ABC):
14
+ """Interface for storing and retrieving shared link configurations."""
15
+
16
+ @abstractmethod
17
+ async def initialize(self) -> None:
18
+ """Initialize the store (e.g., create tables)."""
19
+ pass
20
+
21
+ @abstractmethod
22
+ async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
23
+ """Saves a shared link configuration."""
24
+ pass
25
+
26
+ @abstractmethod
27
+ async def get_config(self, share_id: str) -> SharedLinkConfig | None:
28
+ """Retrieves a shared link configuration by its ID."""
29
+ pass
30
+
31
+ @abstractmethod
32
+ async def delete_config(self, share_id: str) -> bool:
33
+ """Deletes a shared link configuration by its ID. Returns True if deleted, False otherwise."""
34
+ pass
35
+
36
+ class SQLiteSharedLinkStore(SharedLinkStoreInterface):
37
+ """SQLite implementation for storing and retrieving shared link configurations."""
38
+
39
+ def __init__(self, db_path: str):
40
+ self.db_path = Path(db_path)
41
+ self.db_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
42
+ logger.info(f"SQLiteSharedLinkStore initialized with db_path: {self.db_path}")
43
+
44
+ async def initialize(self) -> None:
45
+ """Initializes the database and creates/updates the table if it doesn't exist."""
46
+ try:
47
+ async with aiosqlite.connect(self.db_path) as db:
48
+ # Ensure the table exists with the base schema first
49
+ await db.execute(
50
+ """
51
+ CREATE TABLE IF NOT EXISTS shared_links (
52
+ share_id TEXT PRIMARY KEY,
53
+ agent_name TEXT NOT NULL,
54
+ flock_definition TEXT NOT NULL,
55
+ created_at TEXT NOT NULL
56
+ /* New columns will be added below if they don't exist */
57
+ )
58
+ """
59
+ )
60
+
61
+ # Add new columns individually, ignoring errors if they already exist
62
+ new_columns = [
63
+ ("share_type", "TEXT DEFAULT 'agent_run' NOT NULL"),
64
+ ("chat_message_key", "TEXT"),
65
+ ("chat_history_key", "TEXT"),
66
+ ("chat_response_key", "TEXT")
67
+ ]
68
+
69
+ for column_name, column_type in new_columns:
70
+ try:
71
+ await db.execute(f"ALTER TABLE shared_links ADD COLUMN {column_name} {column_type}")
72
+ logger.info(f"Added column '{column_name}' to shared_links table.")
73
+ except sqlite3.OperationalError as e:
74
+ if "duplicate column name" in str(e).lower():
75
+ logger.debug(f"Column '{column_name}' already exists in shared_links table.")
76
+ else:
77
+ raise # Re-raise if it's a different operational error
78
+
79
+ await db.commit()
80
+ logger.info(f"Database initialized and shared_links table schema ensured at {self.db_path}")
81
+ except sqlite3.Error as e:
82
+ logger.error(f"SQLite error during initialization: {e}", exc_info=True)
83
+ raise
84
+
85
+ async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
86
+ """Saves a shared link configuration to the SQLite database."""
87
+ try:
88
+ async with aiosqlite.connect(self.db_path) as db:
89
+ await db.execute(
90
+ """INSERT INTO shared_links (
91
+ share_id, agent_name, created_at, flock_definition,
92
+ share_type, chat_message_key, chat_history_key, chat_response_key
93
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
94
+ (
95
+ config.share_id,
96
+ config.agent_name,
97
+ config.created_at.isoformat(),
98
+ config.flock_definition,
99
+ config.share_type,
100
+ config.chat_message_key,
101
+ config.chat_history_key,
102
+ config.chat_response_key,
103
+ ),
104
+ )
105
+ await db.commit()
106
+ logger.info(f"Saved shared link config for ID: {config.share_id} with type: {config.share_type}")
107
+ return config
108
+ except sqlite3.Error as e:
109
+ logger.error(f"SQLite error saving config for ID {config.share_id}: {e}", exc_info=True)
110
+ raise
111
+
112
+ async def get_config(self, share_id: str) -> SharedLinkConfig | None:
113
+ """Retrieves a shared link configuration from SQLite by its ID."""
114
+ try:
115
+ async with aiosqlite.connect(self.db_path) as db:
116
+ async with db.execute(
117
+ """SELECT
118
+ share_id, agent_name, created_at, flock_definition,
119
+ share_type, chat_message_key, chat_history_key, chat_response_key
120
+ FROM shared_links WHERE share_id = ?""",
121
+ (share_id,)
122
+ ) as cursor:
123
+ row = await cursor.fetchone()
124
+ if row:
125
+ logger.debug(f"Retrieved shared link config for ID: {share_id}")
126
+ return SharedLinkConfig(
127
+ share_id=row[0],
128
+ agent_name=row[1],
129
+ created_at=row[2], # SQLite stores as TEXT, Pydantic will parse from ISO format
130
+ flock_definition=row[3],
131
+ share_type=row[4],
132
+ chat_message_key=row[5],
133
+ chat_history_key=row[6],
134
+ chat_response_key=row[7],
135
+ )
136
+ logger.debug(f"No shared link config found for ID: {share_id}")
137
+ return None
138
+ except sqlite3.Error as e:
139
+ logger.error(f"SQLite error retrieving config for ID {share_id}: {e}", exc_info=True)
140
+ return None # Or raise, depending on desired error handling
141
+
142
+ async def delete_config(self, share_id: str) -> bool:
143
+ """Deletes a shared link configuration from SQLite by its ID."""
144
+ try:
145
+ async with aiosqlite.connect(self.db_path) as db:
146
+ result = await db.execute("DELETE FROM shared_links WHERE share_id = ?", (share_id,))
147
+ await db.commit()
148
+ deleted_count = result.rowcount
149
+ if deleted_count > 0:
150
+ logger.info(f"Deleted shared link config for ID: {share_id}")
151
+ return True
152
+ logger.info(f"Attempted to delete non-existent shared link config for ID: {share_id}")
153
+ return False
154
+ except sqlite3.Error as e:
155
+ logger.error(f"SQLite error deleting config for ID {share_id}: {e}", exc_info=True)
156
+ return False # Or raise
@@ -240,3 +240,60 @@ body:not(.chat-page) .chat-settings-form .grid button:first-child {
240
240
  main.main-content:has(#chat-container) {
241
241
  overflow: hidden;
242
242
  }
243
+
244
+ /* Ensure all direct text and common markdown elements within bot bubbles use the bot's text color */
245
+ .bubble.bot,
246
+ .bubble.bot p,
247
+ .bubble.bot li,
248
+ .bubble.bot h1,
249
+ .bubble.bot h2,
250
+ .bubble.bot h3,
251
+ .bubble.bot h4,
252
+ .bubble.bot h5,
253
+ .bubble.bot h6,
254
+ .bubble.bot strong,
255
+ .bubble.bot em,
256
+ .bubble.bot table,
257
+ .bubble.bot th,
258
+ .bubble.bot td {
259
+ color: var(--pico-code-color);
260
+ }
261
+
262
+ /* For links specifically within bot messages, you might want them to also use the bot text color */
263
+ .bubble.bot a {
264
+ color: var(--pico-code-color);
265
+ text-decoration: underline; /* Or your preferred link style */
266
+ }
267
+ .bubble.bot a:hover {
268
+ color: var(--pico-primary-hover, var(--pico-primary));
269
+ text-decoration: underline;
270
+ }
271
+
272
+ /* Styling for code blocks generated by Markdown and highlighted by Prism.js */
273
+ /* The prism-okaidia theme will handle the internal colors of the code. */
274
+ /* This is more about the container of the code block. */
275
+ .bubble.bot pre[class*="language-"] {
276
+ background-color: var(--pico-card-background-color); /* Or a slightly different dark shade */
277
+ border: 1px solid var(--pico-muted-border-color);
278
+ border-radius: var(--pico-border-radius);
279
+ padding: 0.75em;
280
+ margin: 0.5em 0;
281
+ font-size: 0.875em; /* Adjust as needed */
282
+ overflow-x: auto; /* Allow horizontal scrolling for long code lines */
283
+ /* The text color *inside* the code block will be handled by the Prism theme (e.g., Okaidia) */
284
+ }
285
+
286
+ /* Ensure the code itself inside the pre block also resets its base color if needed,
287
+ though Prism themes usually take care of this. This is a fallback. */
288
+ .bubble.bot pre[class*="language-"] code {
289
+ /* color: inherit; */ /* This might not be necessary if Prism theme is comprehensive - try without first */
290
+ background: none;
291
+ padding: 0;
292
+ }
293
+
294
+ /* Styles for preformatted text wrapping */
295
+ .bubble pre {
296
+ white-space: pre-wrap; /* Allows wrapping and preserves sequences of white space and newlines */
297
+ word-wrap: break-word; /* Breaks long words/strings if they would overflow */
298
+ overflow-x: auto; /* Adds a scrollbar if content is still too wide */
299
+ }
@@ -6,6 +6,8 @@
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
8
  <link rel="stylesheet" href="/static/css/chat.css">
9
+ {# Prism.js CSS for syntax highlighting (okaidia theme) #}
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css" referrerpolicy="no-referrer" />
9
11
  {# Inject active theme variables #}
10
12
  {% if theme_css %}
11
13
  <style>
@@ -17,6 +19,9 @@
17
19
  </style>
18
20
  {% endif %}
19
21
  <script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
22
+ {# Prism.js JS (core and components for common languages) #}
23
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js" referrerpolicy="no-referrer"></script>
24
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js" referrerpolicy="no-referrer"></script>
20
25
  </head>
21
26
  <body class="chat-page">
22
27
  <div id="chat-container">
@@ -25,21 +30,33 @@
25
30
  <h2>Flock Chat</h2>
26
31
  <h3>{{ chat_subtitle }}</h3>
27
32
  </hgroup>
28
- <button class="secondary outline" hx-get="/chat/htmx/settings-form" hx-target="#chat-content-area" hx-swap="innerHTML" style="min-width:auto;">Settings</button>
33
+ {% if not is_shared_chat %}
34
+ <button class="secondary outline" hx-get="/chat/settings-standalone" hx-target="#chat-content-area" hx-swap="innerHTML" style="min-width:auto;">Settings</button>
35
+ {% else %}
36
+ {# Optionally, could show a disabled settings button or some other indicator #}
37
+ <span style="font-size: 0.8rem; color: var(--pico-muted-color);">Settings frozen for shared session.</span>
38
+ {% endif %}
29
39
  </div>
30
40
 
31
41
  <div id="chat-content-area">
32
- <div id="chat-log" hx-get="/chat/messages" hx-trigger="load" hx-swap="innerHTML">
42
+ <div id="chat-log"
43
+ hx-get="{% if is_shared_chat %}/chat/messages-shared/{{ share_id }}{% else %}/chat/messages{% endif %}"
44
+ hx-trigger="load" {# Polling removed #}
45
+ hx-swap="innerHTML">
33
46
  <p><em>Loading chat…</em></p>
34
47
  </div>
35
48
 
36
49
  <form id="chat-form-standalone"
37
- hx-post="/chat/send"
50
+ hx-post="{% if is_shared_chat %}/chat/send-shared{% else %}/chat/send{% endif %}"
38
51
  hx-target="#chat-log"
39
52
  hx-swap="innerHTML"
40
53
  hx-disabled-elt="input[name='message'], button[type='submit']"
41
54
  hx-on::before-request="htmx.find('#chat-form-standalone button[type=\'submit\']').textContent = 'Sending...'"
42
55
  hx-on::after-request="htmx.find('#chat-form-standalone button[type=\'submit\']').textContent = 'Send'; this.reset();">
56
+
57
+ {% if is_shared_chat %}
58
+ <input type="hidden" name="share_id" value="{{ share_id }}">
59
+ {% endif %}
43
60
  <input type="text" name="message" placeholder="Type a message…" required autofocus>
44
61
  <button type="submit">Send</button>
45
62
  </form>
@@ -57,9 +74,17 @@
57
74
  log.scrollTop = log.scrollHeight;
58
75
  }
59
76
  document.addEventListener('htmx:afterSwap', e => {
60
- if (e.detail.target.id === 'chat-log') scrollBottom();
77
+ if (e.detail.target.id === 'chat-log') {
78
+ scrollBottom();
79
+ // Re-run Prism highlighting after new content is swapped in
80
+ Prism.highlightAllUnder(log);
81
+ }
61
82
  });
62
83
  window.addEventListener('load', scrollBottom);
84
+ // Initial highlight on page load for any pre-existing content
85
+ document.addEventListener('DOMContentLoaded', () => {
86
+ Prism.highlightAll();
87
+ });
63
88
  })();
64
89
  </script>
65
90
  </body>
@@ -6,7 +6,7 @@
6
6
  </div>
7
7
  {% else %}
8
8
  <div class="bubble bot">
9
- {{ entry.text }}
9
+ {{ entry.text | safe }}
10
10
  <span class="chat-timestamp">{{ entry.timestamp|default(now().strftime('%H:%M')) }}{% if entry.agent %} - {{ entry.agent }}{% endif %}{% if entry.duration_ms is defined %} - {{ entry.duration_ms }}ms{% endif %}</span>
11
11
  </div>
12
12
  {% endif %}
@@ -62,4 +62,26 @@
62
62
  <button type="button" class="secondary outline" hx-get="/chat/htmx/chat-view" hx-target="#chat-content-area" hx-swap="innerHTML">Cancel</button>
63
63
  </div>
64
64
  <div id="chat-settings-saving" class="htmx-indicator" style="text-align:center; margin-top:0.5rem;"><progress indeterminate></progress></div>
65
+
66
+ {# Share Chat Link Section #}
67
+ <div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--pico-muted-border-color);">
68
+ <h5 style="margin-bottom: 0.5rem;">Share Chat Configuration</h5>
69
+ <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
+ <a href="#"
71
+ id="shareChatHtmxLink"
72
+ hx-post="/ui/htmx/share/chat/generate-link"
73
+ hx-target="#shareChatLinkDisplayArea"
74
+ hx-swap="innerHTML"
75
+ hx-indicator="#share-chat-loading-indicator"
76
+ hx-include="closest form" {# Includes all fields from the parent form #}
77
+ style="text-decoration: underline; cursor: pointer; color: var(--pico-primary);">
78
+ Create shareable chat link...
79
+ </a>
80
+ <span id="share-chat-loading-indicator" class="htmx-indicator" style="margin-left: 0.5rem;">
81
+ <progress indeterminate style="width: 100px;"></progress>
82
+ </span>
83
+ <div id="shareChatLinkDisplayArea" style="margin-top: 1rem;">
84
+ <!-- Shareable chat link will be loaded here by HTMX -->
85
+ </div>
86
+ </div>
65
87
  </form>
@@ -36,6 +36,20 @@
36
36
  </div>
37
37
 
38
38
  <button type="submit" {% if not flock.agents %}disabled{% endif %}>Run Flock</button>
39
+
40
+ <div id="share-agent-link-container" style="margin-top: 0.5rem;">
41
+ <a href="#"
42
+ id="shareAgentHtmxLink"
43
+ hx-post="/ui/htmx/share/generate-link"
44
+ hx-target="#shareLinkDisplayArea"
45
+ hx-swap="innerHTML"
46
+ hx-indicator="#share-loading-indicator"
47
+ hx-include="#start_agent_name_select"
48
+ style="text-decoration: underline; cursor: pointer; color: var(--pico-primary);">
49
+ Create shareable link...
50
+ </a>
51
+ </div>
52
+
39
53
  <span id="run-loading-indicator" class="htmx-indicator">
40
54
  <progress indeterminate></progress> Running...
41
55
  </span>
@@ -45,4 +59,17 @@
45
59
  {% else %}
46
60
  <p>Load or create a Flock to enable execution.</p>
47
61
  {% endif %}
48
- </article>
62
+
63
+ <div id="shareLinkDisplayArea" style="margin-top: 1rem;">
64
+ <!-- Content will be loaded here by HTMX -->
65
+ </div>
66
+
67
+ </article>
68
+
69
+ <script>
70
+ document.addEventListener('DOMContentLoaded', function() {
71
+ // All previous JavaScript for toggling share link visibility is removed.
72
+ // If there are other unrelated JavaScript functions in this script block,
73
+ // they would remain.
74
+ });
75
+ </script>
@@ -0,0 +1,11 @@
1
+ {# This snippet is returned by the /ui/htmx/share/chat/generate-link endpoint #}
2
+ {% if share_url %}
3
+ <p style="margin-bottom: 0.5rem;"><strong>Shareable Chat Link generated:</strong></p>
4
+ <input type="text" id="generatedShareChatLinkInput" value="{{ share_url }}" readonly style="width: 100%; margin-bottom: 0.5rem;">
5
+ <p><small>You can select the link above and copy it (Ctrl+C or Cmd+C).</small></p>
6
+ <p><small>This link will start a chat session with the agent '{{ agent_name }}' from Flock '{{ flock_name }}' using the currently saved settings.</small></p>
7
+ {% elif error_message %}
8
+ <p style="color: var(--pico-form-invalid-color);">Error generating chat link: {{ error_message }}</p>
9
+ {% else %}
10
+ <p><em>Something went wrong, no chat link generated.</em></p>
11
+ {% endif %}
@@ -0,0 +1,35 @@
1
+ {# This snippet is returned by the /ui/htmx/share/generate-link endpoint #}
2
+ {% if share_url %}
3
+ <p style="margin-bottom: 0.5rem;"><strong>Shareable Link generated:</strong></p>
4
+ <input type="text" id="generatedShareLinkInput" value="{{ share_url }}" readonly style="width: 100%; margin-bottom: 0.5rem;">
5
+ {# <button type="button" onclick="copyGeneratedLinkToClipboard()">Copy Link</button> #}
6
+ {# <small id="copyGeneratedStatusMsg" style="margin-left: 0.5rem;"></small> #}
7
+ <p><small>You can select the link above and copy it (Ctrl+C or Cmd+C).</small></p>
8
+ {% elif error_message %}
9
+ <p style="color: var(--pico-form-invalid-color);">Error: {{ error_message }}</p>
10
+ {% else %}
11
+ <p><em>Something went wrong, no link generated.</em></p>
12
+ {% endif %}
13
+
14
+ {#
15
+ <script>
16
+ // Minimal JS for copy if desired - but user asked for JS-free.
17
+ // If you re-add the button, uncomment this.
18
+ function copyGeneratedLinkToClipboard() {
19
+ const input = document.getElementById('generatedShareLinkInput');
20
+ const msg = document.getElementById('copyGeneratedStatusMsg');
21
+ if (!input || !msg) return;
22
+
23
+ input.select();
24
+ input.setSelectionRange(0, 99999); // For mobile devices
25
+ try {
26
+ document.execCommand('copy');
27
+ msg.textContent = 'Copied!';
28
+ setTimeout(() => { msg.textContent = ''; }, 2000);
29
+ } catch (err) {
30
+ console.error('Failed to copy text: ', err);
31
+ msg.textContent = 'Failed to copy.';
32
+ }
33
+ }
34
+ </script>
35
+ #}
@@ -0,0 +1,116 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Flock Shared Agent: {{ selected_agent_name }}</title>
7
+
8
+ {# Link to Pico.css #}
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" />
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
+ <!-- Font Awesome for icons -->
16
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
17
+ {# Inline generated theme CSS variables #}
18
+ {% if theme_css %}
19
+ <style>
20
+ /* Start Theme CSS */
21
+ /* stylelint-disable */
22
+ {{ theme_css | safe }}
23
+ /* stylelint-enable */
24
+ /* End Theme CSS */
25
+ </style>
26
+ {% endif %}
27
+
28
+
29
+ {# HTMX script - ensure this is loaded for the page to work #}
30
+ <script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
31
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
32
+ </head>
33
+ <body>
34
+ <main class="main-content">
35
+ <header>
36
+ <hgroup>
37
+ <h2>Run {{ flock.name }} - {{ selected_agent_name }} </h2>
38
+ </hgroup>
39
+ </header>
40
+
41
+ {% if error_message %}
42
+ <article class="error-message" role="alert">
43
+ <strong>Error:</strong> {{ error_message }}
44
+ </article>
45
+ {% endif %}
46
+
47
+ {% if selected_agent_name and not error_message %} {# Only show form if no fatal error and agent is selected #}
48
+ <div style="display: flex; gap: var(--pico-spacing, 1rem);">
49
+ <div style="flex: 1;">
50
+ <article id="execution-form-content">
51
+ <form id="agent-run-form-shared"
52
+ hx-post="/ui/api/flock/htmx/run-shared"
53
+ hx-target="#results-display"
54
+ hx-swap="innerHTML"
55
+ hx-indicator="#run-loading-indicator">
56
+
57
+ {# Hidden input for the fixed agent name #}
58
+ <input type="hidden" name="start_agent_name" value="{{ selected_agent_name }}">
59
+
60
+ {# Add share_id as a hidden input to be sent with the form #}
61
+ <input type="hidden" name="share_id" value="{{ share_id }}">
62
+
63
+ {# flock_definition_str hidden input is no longer needed #}
64
+ {# {% if flock_definition_str %} #}
65
+ {# <input type="hidden" name="flock_definition_str" value="{{ flock_definition_str }}"> #}
66
+ {# {% endif %} #}
67
+
68
+ {# Dynamically generated input fields #}
69
+ {% if input_fields %}
70
+ <h4>Inputs for <code>{{ selected_agent_name }}</code>:</h4>
71
+ {% for field in input_fields %}
72
+ <label for="agent_input_{{ field.name }}">
73
+ {{ field.name }} ({{ field.type }})<br>
74
+ {% if field.description %}<small>{{ field.description }}</small>{% endif %}
75
+ </label>
76
+ {% if field.html_type == "checkbox" %}
77
+ <input type="checkbox" id="agent_input_{{ field.name }}" name="agent_input_{{ field.name }}" role="switch">
78
+ {% elif field.html_type == "textarea" %}
79
+ <textarea id="agent_input_{{ field.name }}" name="agent_input_{{ field.name }}" placeholder="{{ field.placeholder | default('Enter value') }}"></textarea>
80
+ {% else %}
81
+ <input type="{{ field.html_type }}" id="agent_input_{{ field.name }}" name="agent_input_{{ field.name }}" placeholder="Enter {{ field.type }} value">
82
+ {% endif %}
83
+ {% endfor %}
84
+ {% elif flock and selected_agent_name in flock.agents and not flock.agents[selected_agent_name].input %}
85
+ <p>Agent <code>{{ selected_agent_name }}</code> requires no inputs.</p>
86
+ {% elif not error_message %}
87
+ <p>Could not determine inputs for agent <code>{{ selected_agent_name }}</code>. The input signature might be missing or invalid.</p>
88
+ {% endif %}
89
+
90
+ <button type="submit" class="hide-on-request">Run Agent</button>
91
+ <span id="run-loading-indicator" class="htmx-indicator">
92
+ <progress indeterminate></progress> Running...
93
+ </span>
94
+ </form>
95
+ </article>
96
+ </div>
97
+ <section class="right-pane-framed" style="flex: 2; border-left: 1px solid var(--pico-muted-border-color); padding-left: 1.5rem;">
98
+ <header style=" border-bottom: 1px solid var(--pico-muted-border-color); margin-bottom: 1rem;">
99
+ <h5>Execution Results</h5>
100
+ </header>
101
+ <div id="results-display">
102
+ <p><code>Results will appear here after running the Flock.</code></p>
103
+ </div>
104
+ </section>
105
+ </div>
106
+
107
+ {% elif not error_message %}
108
+ <p>This shared link is not configured correctly. No agent specified or an issue occurred loading the configuration.</p>
109
+ {% endif %}
110
+
111
+ <footer class="main-footer">
112
+ <small>Built with FastAPI, HTMX, Pico.CSS by 🤍 white duck 🦆 - Theme: {{ active_theme_name | default('default') }}</small>
113
+ </footer>
114
+ </main>
115
+ </body>
116
+ </html>