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.
- flock/__init__.py +45 -3
- flock/core/flock.py +105 -61
- flock/core/flock_registry.py +45 -38
- flock/core/util/spliter.py +4 -0
- flock/evaluators/__init__.py +1 -0
- flock/evaluators/declarative/__init__.py +1 -0
- flock/modules/__init__.py +1 -0
- flock/modules/assertion/__init__.py +1 -0
- flock/modules/callback/__init__.py +1 -0
- flock/modules/mem0/__init__.py +1 -0
- flock/modules/mem0/mem0_module.py +63 -0
- flock/modules/mem0graph/__init__.py +1 -0
- flock/modules/mem0graph/mem0_graph_module.py +63 -0
- flock/modules/memory/__init__.py +1 -0
- flock/modules/output/__init__.py +1 -0
- flock/modules/performance/__init__.py +1 -0
- flock/tools/__init__.py +188 -0
- flock/{core/tools → tools}/azure_tools.py +284 -0
- flock/tools/code_tools.py +56 -0
- flock/tools/file_tools.py +140 -0
- flock/{core/tools/dev_tools/github.py → tools/github_tools.py} +3 -3
- flock/{core/tools → tools}/markdown_tools.py +14 -4
- flock/tools/system_tools.py +9 -0
- flock/{core/tools/llm_tools.py → tools/text_tools.py} +47 -25
- flock/tools/web_tools.py +90 -0
- flock/{core/tools → tools}/zendesk_tools.py +6 -6
- flock/webapp/app/api/execution.py +130 -30
- flock/webapp/app/chat.py +303 -16
- flock/webapp/app/config.py +15 -1
- flock/webapp/app/dependencies.py +22 -0
- flock/webapp/app/main.py +509 -18
- flock/webapp/app/services/flock_service.py +38 -13
- flock/webapp/app/services/sharing_models.py +43 -0
- flock/webapp/app/services/sharing_store.py +156 -0
- flock/webapp/static/css/chat.css +57 -0
- flock/webapp/templates/chat.html +29 -4
- flock/webapp/templates/partials/_chat_messages.html +1 -1
- flock/webapp/templates/partials/_chat_settings_form.html +22 -0
- flock/webapp/templates/partials/_execution_form.html +28 -1
- flock/webapp/templates/partials/_share_chat_link_snippet.html +11 -0
- flock/webapp/templates/partials/_share_link_snippet.html +35 -0
- flock/webapp/templates/shared_run_page.html +116 -0
- flock/workflow/activities.py +1 -0
- {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/METADATA +27 -14
- {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/RECORD +48 -28
- flock/core/tools/basic_tools.py +0 -317
- flock/modules/zep/zep_module.py +0 -187
- {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b46.dist-info → flock_core-0.4.0b49.dist-info}/entry_points.txt +0 -0
- {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,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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("
|
|
257
|
-
return "
|
|
258
|
-
if not
|
|
259
|
-
logger.error(
|
|
260
|
-
|
|
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"
|
|
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"
|
|
269
|
-
return f"
|
|
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
|
flock/webapp/static/css/chat.css
CHANGED
|
@@ -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
|
+
}
|
flock/webapp/templates/chat.html
CHANGED
|
@@ -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
|
-
|
|
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"
|
|
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')
|
|
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
|
-
|
|
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>
|