flock-core 0.4.540__py3-none-any.whl → 0.4.541__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/core/logging/live_capture.py +137 -0
- flock/webapp/app/api/agent_management.py +8 -12
- flock/webapp/app/api/execution.py +4 -8
- flock/webapp/app/api/flock_management.py +4 -8
- flock/webapp/app/api/registry_viewer.py +1 -2
- flock/webapp/app/chat.py +14 -17
- flock/webapp/app/main.py +84 -58
- flock/webapp/app/theme_mapper.py +1 -2
- flock/webapp/run.py +4 -0
- flock/webapp/static/css/layout.css +239 -4
- flock/webapp/templates/base.html +192 -3
- flock/webapp/templates/partials/_live_logs.html +13 -0
- {flock_core-0.4.540.dist-info → flock_core-0.4.541.dist-info}/METADATA +1 -1
- {flock_core-0.4.540.dist-info → flock_core-0.4.541.dist-info}/RECORD +17 -15
- {flock_core-0.4.540.dist-info → flock_core-0.4.541.dist-info}/WHEEL +0 -0
- {flock_core-0.4.540.dist-info → flock_core-0.4.541.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.540.dist-info → flock_core-0.4.541.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from collections import deque
|
|
9
|
+
from typing import Deque, List, Literal, MutableMapping
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"enable_live_log_capture",
|
|
13
|
+
"get_live_log_store",
|
|
14
|
+
"LiveLogStore",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
AnsiSource = Literal["stdout", "stderr"]
|
|
18
|
+
ANSI_ESCAPE_PATTERN = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LiveLogStore:
|
|
22
|
+
"""Thread-safe ring buffer that keeps recent CLI log lines."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, max_lines: int = 800) -> None:
|
|
25
|
+
self._lines: Deque[dict[str, object]] = deque(maxlen=max_lines)
|
|
26
|
+
self._buffers: MutableMapping[AnsiSource, str] = {"stdout": "", "stderr": ""}
|
|
27
|
+
self._lock = threading.Lock()
|
|
28
|
+
|
|
29
|
+
def append_chunk(self, chunk: str, source: AnsiSource) -> None:
|
|
30
|
+
"""Append raw stream data, splitting into sanitized log lines."""
|
|
31
|
+
if not chunk:
|
|
32
|
+
return
|
|
33
|
+
normalized = chunk.replace("\r\n", "\n").replace("\r", "\n")
|
|
34
|
+
with self._lock:
|
|
35
|
+
combined = self._buffers[source] + normalized
|
|
36
|
+
parts = combined.split("\n")
|
|
37
|
+
if combined.endswith("\n"):
|
|
38
|
+
complete, remainder = parts[:-1], ""
|
|
39
|
+
else:
|
|
40
|
+
complete, remainder = parts[:-1], parts[-1]
|
|
41
|
+
self._buffers[source] = remainder
|
|
42
|
+
|
|
43
|
+
timestamp = time.time()
|
|
44
|
+
for raw_line in complete:
|
|
45
|
+
cleaned = ANSI_ESCAPE_PATTERN.sub("", raw_line).rstrip("\r")
|
|
46
|
+
# Preserve deliberate blank lines but normalise whitespace-only strings
|
|
47
|
+
if cleaned and cleaned.strip() == "":
|
|
48
|
+
cleaned = ""
|
|
49
|
+
self._lines.append(
|
|
50
|
+
{
|
|
51
|
+
"timestamp": timestamp,
|
|
52
|
+
"stream": source,
|
|
53
|
+
"text": cleaned,
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
timestamp += 1e-6 # keep ordering stable for same chunk
|
|
57
|
+
|
|
58
|
+
def get_entries(self, *, limit: int | None = None) -> List[dict[str, object]]:
|
|
59
|
+
"""Return a copy of the most recent log entries (oldest first)."""
|
|
60
|
+
with self._lock:
|
|
61
|
+
snapshot = list(self._lines)
|
|
62
|
+
if limit is None:
|
|
63
|
+
return snapshot
|
|
64
|
+
if limit <= 0:
|
|
65
|
+
return []
|
|
66
|
+
return snapshot[-limit:]
|
|
67
|
+
|
|
68
|
+
def clear(self) -> None:
|
|
69
|
+
"""Clear buffered log lines and pending chunks."""
|
|
70
|
+
with self._lock:
|
|
71
|
+
self._lines.clear()
|
|
72
|
+
self._buffers = {"stdout": "", "stderr": ""}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class _TeeStream(io.TextIOBase):
|
|
76
|
+
"""Duplicate writes to the original stream and the live log store."""
|
|
77
|
+
|
|
78
|
+
def __init__(self, wrapped: io.TextIOBase, source: AnsiSource, store: LiveLogStore) -> None:
|
|
79
|
+
self._wrapped = wrapped
|
|
80
|
+
self._source = source
|
|
81
|
+
self._store = store
|
|
82
|
+
|
|
83
|
+
def write(self, data: str) -> int: # type: ignore[override]
|
|
84
|
+
self._store.append_chunk(data, self._source)
|
|
85
|
+
return self._wrapped.write(data)
|
|
86
|
+
|
|
87
|
+
def flush(self) -> None: # type: ignore[override]
|
|
88
|
+
self._wrapped.flush()
|
|
89
|
+
|
|
90
|
+
def isatty(self) -> bool: # type: ignore[override]
|
|
91
|
+
return self._wrapped.isatty()
|
|
92
|
+
|
|
93
|
+
def fileno(self) -> int: # type: ignore[override]
|
|
94
|
+
return self._wrapped.fileno()
|
|
95
|
+
|
|
96
|
+
def readable(self) -> bool: # type: ignore[override]
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
def writable(self) -> bool: # type: ignore[override]
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
def seekable(self) -> bool: # type: ignore[override]
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def close(self) -> None: # type: ignore[override]
|
|
106
|
+
self._wrapped.close()
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def closed(self) -> bool: # type: ignore[override]
|
|
110
|
+
return self._wrapped.closed
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def encoding(self) -> str | None: # type: ignore[override]
|
|
114
|
+
return getattr(self._wrapped, "encoding", None)
|
|
115
|
+
|
|
116
|
+
def __getattr__(self, item: str):
|
|
117
|
+
return getattr(self._wrapped, item)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
_live_log_store = LiveLogStore()
|
|
121
|
+
_capture_enabled = False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def enable_live_log_capture() -> None:
|
|
125
|
+
"""Wrap stdout/stderr so CLI output is mirrored into the live log store."""
|
|
126
|
+
global _capture_enabled
|
|
127
|
+
if _capture_enabled:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
sys.stdout = _TeeStream(sys.stdout, "stdout", _live_log_store)
|
|
131
|
+
sys.stderr = _TeeStream(sys.stderr, "stderr", _live_log_store)
|
|
132
|
+
_capture_enabled = True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_live_log_store() -> LiveLogStore:
|
|
136
|
+
"""Expose the singleton live log store for dependency injection."""
|
|
137
|
+
return _live_log_store
|
|
@@ -49,8 +49,7 @@ async def htmx_get_agent_list(
|
|
|
49
49
|
# For a partial, returning an error or empty state is reasonable.
|
|
50
50
|
return HTMLResponse("<div id='agent-list-container'><p class='error'>No Flock loaded to display agents.</p></div>", headers={"HX-Retarget": "#agent-list-container", "HX-Reswap": "innerHTML"})
|
|
51
51
|
|
|
52
|
-
return templates.TemplateResponse(
|
|
53
|
-
"partials/_agent_list.html",
|
|
52
|
+
return templates.TemplateResponse(request, "partials/_agent_list.html",
|
|
54
53
|
{
|
|
55
54
|
"request": request,
|
|
56
55
|
"flock": current_flock, # Pass the injected flock instance
|
|
@@ -78,8 +77,7 @@ async def htmx_get_agent_details_form(
|
|
|
78
77
|
[tool.__name__ for tool in agent.tools] if agent.tools else []
|
|
79
78
|
)
|
|
80
79
|
|
|
81
|
-
return templates.TemplateResponse(
|
|
82
|
-
"partials/_agent_detail_form.html",
|
|
80
|
+
return templates.TemplateResponse(request, "partials/_agent_detail_form.html",
|
|
83
81
|
{
|
|
84
82
|
"request": request,
|
|
85
83
|
"agent": agent,
|
|
@@ -97,8 +95,7 @@ async def htmx_get_new_agent_form(
|
|
|
97
95
|
):
|
|
98
96
|
# current_flock is injected, primarily to ensure context if needed by template/tools list
|
|
99
97
|
registered_tools = get_registered_items_service("tool")
|
|
100
|
-
return templates.TemplateResponse(
|
|
101
|
-
"partials/_agent_detail_form.html",
|
|
98
|
+
return templates.TemplateResponse(request, "partials/_agent_detail_form.html",
|
|
102
99
|
{
|
|
103
100
|
"request": request,
|
|
104
101
|
"agent": None,
|
|
@@ -123,8 +120,7 @@ async def htmx_create_agent(
|
|
|
123
120
|
# The service function add_agent_to_current_flock_service now takes app_state
|
|
124
121
|
if (not agent_name.strip() or not input_signature.strip() or not output_signature.strip()):
|
|
125
122
|
registered_tools = get_registered_items_service("tool")
|
|
126
|
-
return templates.TemplateResponse(
|
|
127
|
-
"partials/_agent_detail_form.html",
|
|
123
|
+
return templates.TemplateResponse(request, "partials/_agent_detail_form.html",
|
|
128
124
|
{
|
|
129
125
|
"request": request, "agent": None, "is_new": True,
|
|
130
126
|
"error_message": "Name, Input Signature, and Output Signature are required.",
|
|
@@ -153,7 +149,7 @@ async def htmx_create_agent(
|
|
|
153
149
|
"form_message": "Agent created successfully!" if success else "Failed to create agent. Check logs.",
|
|
154
150
|
"success": success,
|
|
155
151
|
}
|
|
156
|
-
return templates.TemplateResponse("partials/_agent_detail_form.html", new_form_context, headers=response_headers)
|
|
152
|
+
return templates.TemplateResponse(request, "partials/_agent_detail_form.html", new_form_context, headers=response_headers)
|
|
157
153
|
|
|
158
154
|
|
|
159
155
|
@router.put("/htmx/agents/{original_agent_name}", response_class=HTMLResponse)
|
|
@@ -196,7 +192,7 @@ async def htmx_update_agent(
|
|
|
196
192
|
"success": success,
|
|
197
193
|
"registered_tools": registered_tools, "current_tools": current_agent_tools,
|
|
198
194
|
}
|
|
199
|
-
return templates.TemplateResponse("partials/_agent_detail_form.html", updated_form_context, headers=response_headers)
|
|
195
|
+
return templates.TemplateResponse(request, "partials/_agent_detail_form.html", updated_form_context, headers=response_headers)
|
|
200
196
|
|
|
201
197
|
|
|
202
198
|
@router.delete("/htmx/agents/{agent_name}", response_class=HTMLResponse)
|
|
@@ -219,7 +215,7 @@ async def htmx_delete_agent(
|
|
|
219
215
|
# "form_message": f"Agent '{agent_name}' removed.", # Message handled by notify
|
|
220
216
|
# "success": True, # Not strictly needed if form is cleared
|
|
221
217
|
}
|
|
222
|
-
return templates.TemplateResponse("partials/_agent_detail_form.html", empty_form_context, headers=response_headers)
|
|
218
|
+
return templates.TemplateResponse(request, "partials/_agent_detail_form.html", empty_form_context, headers=response_headers)
|
|
223
219
|
else:
|
|
224
220
|
# Deletion failed, re-render the form for the agent that failed to delete (if it still exists)
|
|
225
221
|
flock_instance_from_state: Flock | None = getattr(request.app.state, 'flock_instance', None)
|
|
@@ -238,4 +234,4 @@ async def htmx_delete_agent(
|
|
|
238
234
|
}
|
|
239
235
|
# Trigger a notification for the error as well
|
|
240
236
|
response_headers["HX-Trigger"] = json.dumps({"notify": {"type":"error", "message": f"Failed to remove agent '{agent_name}'."}})
|
|
241
|
-
return templates.TemplateResponse("partials/_agent_detail_form.html", error_form_context, headers=response_headers)
|
|
237
|
+
return templates.TemplateResponse(request, "partials/_agent_detail_form.html", error_form_context, headers=response_headers)
|
|
@@ -67,8 +67,7 @@ async def htmx_get_execution_form_content(
|
|
|
67
67
|
), # Use optional if form can show 'no flock'
|
|
68
68
|
):
|
|
69
69
|
# flock instance is injected
|
|
70
|
-
return templates.TemplateResponse(
|
|
71
|
-
"partials/_execution_form.html",
|
|
70
|
+
return templates.TemplateResponse(request, "partials/_execution_form.html",
|
|
72
71
|
{
|
|
73
72
|
"request": request,
|
|
74
73
|
"flock": current_flock, # Pass the injected flock instance
|
|
@@ -123,8 +122,7 @@ async def htmx_get_agent_input_form(
|
|
|
123
122
|
return HTMLResponse(
|
|
124
123
|
f"<p class='error'>Error parsing input signature for {agent_name}: {e}</p>"
|
|
125
124
|
)
|
|
126
|
-
return templates.TemplateResponse(
|
|
127
|
-
"partials/_dynamic_input_form_content.html",
|
|
125
|
+
return templates.TemplateResponse(request, "partials/_dynamic_input_form_content.html",
|
|
128
126
|
{"request": request, "input_fields": input_fields},
|
|
129
127
|
)
|
|
130
128
|
|
|
@@ -223,8 +221,7 @@ async def htmx_run_flock(
|
|
|
223
221
|
# Unescape newlines for proper display in HTML <pre> tag
|
|
224
222
|
result_data_raw_json_str = raw_json_for_template.replace("\\n", "\n")
|
|
225
223
|
root_path = request.scope.get("root_path", "")
|
|
226
|
-
return templates.TemplateResponse(
|
|
227
|
-
"partials/_results_display.html",
|
|
224
|
+
return templates.TemplateResponse(request, "partials/_results_display.html",
|
|
228
225
|
{
|
|
229
226
|
"request": request,
|
|
230
227
|
"result": result_data,
|
|
@@ -344,8 +341,7 @@ async def htmx_run_shared_flock(
|
|
|
344
341
|
)
|
|
345
342
|
root_path = request.scope.get("root_path", "")
|
|
346
343
|
|
|
347
|
-
return templates.TemplateResponse(
|
|
348
|
-
"partials/_results_display.html",
|
|
344
|
+
return templates.TemplateResponse(request, "partials/_results_display.html",
|
|
349
345
|
{
|
|
350
346
|
"request": request,
|
|
351
347
|
"result": result_data,
|
|
@@ -42,8 +42,7 @@ async def htmx_get_flock_properties_form(
|
|
|
42
42
|
return HTMLResponse(
|
|
43
43
|
"<div class='error'>Error: No flock loaded. Please load or create one first.</div>"
|
|
44
44
|
)
|
|
45
|
-
return templates.TemplateResponse(
|
|
46
|
-
"partials/_flock_properties_form.html",
|
|
45
|
+
return templates.TemplateResponse(request, "partials/_flock_properties_form.html",
|
|
47
46
|
{
|
|
48
47
|
"request": request,
|
|
49
48
|
"flock": current_flock,
|
|
@@ -71,8 +70,7 @@ async def htmx_update_flock_properties(
|
|
|
71
70
|
updated_flock: Flock | None = getattr(request.app.state, 'flock_instance', None)
|
|
72
71
|
updated_filename: str | None = getattr(request.app.state, 'flock_filename', None)
|
|
73
72
|
|
|
74
|
-
return templates.TemplateResponse(
|
|
75
|
-
"partials/_flock_properties_form.html",
|
|
73
|
+
return templates.TemplateResponse(request, "partials/_flock_properties_form.html",
|
|
76
74
|
{
|
|
77
75
|
"request": request,
|
|
78
76
|
"flock": updated_flock,
|
|
@@ -95,8 +93,7 @@ async def htmx_save_flock(
|
|
|
95
93
|
current_filename_from_state: str | None = getattr(request.app.state, 'flock_filename', None)
|
|
96
94
|
|
|
97
95
|
if not save_filename.strip():
|
|
98
|
-
return templates.TemplateResponse(
|
|
99
|
-
"partials/_flock_properties_form.html",
|
|
96
|
+
return templates.TemplateResponse(request, "partials/_flock_properties_form.html",
|
|
100
97
|
{
|
|
101
98
|
"request": request,
|
|
102
99
|
"flock": current_flock_from_state,
|
|
@@ -117,8 +114,7 @@ async def htmx_save_flock(
|
|
|
117
114
|
saved_filename: str | None = getattr(request.app.state, 'flock_filename', None)
|
|
118
115
|
|
|
119
116
|
|
|
120
|
-
return templates.TemplateResponse(
|
|
121
|
-
"partials/_flock_properties_form.html",
|
|
117
|
+
return templates.TemplateResponse(request, "partials/_flock_properties_form.html",
|
|
122
118
|
{
|
|
123
119
|
"request": request,
|
|
124
120
|
"flock": saved_flock, # Use the instance from app_state
|
|
@@ -20,8 +20,7 @@ async def htmx_get_registry_table(request: Request, item_type: str):
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
items = get_registered_items_service(item_type)
|
|
23
|
-
return templates.TemplateResponse(
|
|
24
|
-
"partials/_registry_table.html",
|
|
23
|
+
return templates.TemplateResponse(request, "partials/_registry_table.html",
|
|
25
24
|
{
|
|
26
25
|
"request": request,
|
|
27
26
|
"item_type_display": item_type.capitalize() + "s",
|
flock/webapp/app/chat.py
CHANGED
|
@@ -140,7 +140,7 @@ async def chat_page(request: Request):
|
|
|
140
140
|
cfg = get_chat_config(request)
|
|
141
141
|
context = get_base_context_web(request, ui_mode="standalone")
|
|
142
142
|
context.update({"history": history, "chat_cfg": cfg, "chat_subtitle": f"Agent: {cfg.agent_name}" if cfg.agent_name else "Echo demo", "is_shared_chat": False, "share_id": None})
|
|
143
|
-
response = templates.TemplateResponse("chat.html", context)
|
|
143
|
+
response = templates.TemplateResponse(request, "chat.html", context)
|
|
144
144
|
# Set cookie if not already present
|
|
145
145
|
if COOKIE_NAME not in request.cookies:
|
|
146
146
|
response.set_cookie(COOKIE_NAME, sid, max_age=60 * 60 * 24 * 7)
|
|
@@ -152,6 +152,7 @@ async def chat_history_partial(request: Request):
|
|
|
152
152
|
"""HTMX endpoint that returns the rendered message list."""
|
|
153
153
|
_, history = _ensure_session(request)
|
|
154
154
|
return templates.TemplateResponse(
|
|
155
|
+
request,
|
|
155
156
|
"partials/_chat_messages.html",
|
|
156
157
|
{"request": request, "history": history, "now": datetime.now}
|
|
157
158
|
)
|
|
@@ -244,8 +245,7 @@ async def chat_send(request: Request, message: str = Form(...)):
|
|
|
244
245
|
"flock_yaml": getattr(flock_inst, 'to_yaml', lambda: "")()
|
|
245
246
|
})
|
|
246
247
|
# Return updated history partial
|
|
247
|
-
return templates.TemplateResponse(
|
|
248
|
-
"partials/_chat_messages.html",
|
|
248
|
+
return templates.TemplateResponse(request, "partials/_chat_messages.html",
|
|
249
249
|
{"request": request, "history": history, "now": datetime.now}
|
|
250
250
|
)
|
|
251
251
|
|
|
@@ -253,7 +253,7 @@ async def chat_send(request: Request, message: str = Form(...)):
|
|
|
253
253
|
@router.get("/ui/htmx/chat-view", response_class=HTMLResponse, tags=["Chat"], include_in_schema=False)
|
|
254
254
|
async def chat_container_partial(request: Request):
|
|
255
255
|
_ensure_session(request)
|
|
256
|
-
return templates.TemplateResponse("partials/_chat_container.html", {"request": request})
|
|
256
|
+
return templates.TemplateResponse(request, "partials/_chat_container.html", {"request": request})
|
|
257
257
|
|
|
258
258
|
|
|
259
259
|
# ---------------------------------------------------------------------------
|
|
@@ -287,7 +287,7 @@ async def chat_settings_form(request: Request):
|
|
|
287
287
|
"input_fields": input_fields,
|
|
288
288
|
"output_fields": output_fields,
|
|
289
289
|
})
|
|
290
|
-
return templates.TemplateResponse("partials/_chat_settings_form.html", context)
|
|
290
|
+
return templates.TemplateResponse(request, "partials/_chat_settings_form.html", context)
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
@router.post("/chat/settings", include_in_schema=False)
|
|
@@ -329,7 +329,7 @@ async def chat_settings_standalone(request: Request):
|
|
|
329
329
|
"chat_cfg": cfg,
|
|
330
330
|
"current_flock": getattr(request.app.state, "flock_instance", None),
|
|
331
331
|
})
|
|
332
|
-
return templates.TemplateResponse("chat_settings.html", context)
|
|
332
|
+
return templates.TemplateResponse(request, "chat_settings.html", context)
|
|
333
333
|
|
|
334
334
|
|
|
335
335
|
# ---------------------------------------------------------------------------
|
|
@@ -341,7 +341,7 @@ async def chat_settings_standalone(request: Request):
|
|
|
341
341
|
async def htmx_chat_view(request: Request):
|
|
342
342
|
"""Return chat container partial for standalone page reload via HTMX."""
|
|
343
343
|
_ensure_session(request)
|
|
344
|
-
return templates.TemplateResponse("partials/_chat_container.html", {"request": request})
|
|
344
|
+
return templates.TemplateResponse(request, "partials/_chat_container.html", {"request": request})
|
|
345
345
|
|
|
346
346
|
|
|
347
347
|
@router.get("/chat/htmx/settings-form", response_class=HTMLResponse, include_in_schema=False)
|
|
@@ -363,7 +363,7 @@ async def htmx_chat_settings_partial(request: Request):
|
|
|
363
363
|
output_fields = _extract(agent_obj.output) if getattr(agent_obj, 'output', '') else []
|
|
364
364
|
|
|
365
365
|
context = {"request": request, "chat_cfg": cfg, "current_flock": flock_inst, "input_fields": input_fields, "output_fields": output_fields}
|
|
366
|
-
return templates.TemplateResponse("partials/_chat_settings_form.html", context)
|
|
366
|
+
return templates.TemplateResponse(request, "partials/_chat_settings_form.html", context)
|
|
367
367
|
|
|
368
368
|
|
|
369
369
|
# ---------------------------------------------------------------------------
|
|
@@ -387,7 +387,7 @@ async def page_shared_chat(
|
|
|
387
387
|
logger.warning(f"Shared chat link {share_id} not found or not a chat share type.")
|
|
388
388
|
# Consider rendering an error template or redirecting
|
|
389
389
|
error_context = get_base_context_web(request, ui_mode="standalone", error="Shared chat link is invalid or has expired.")
|
|
390
|
-
return templates.TemplateResponse("error_page.html", {**error_context, "error_title": "Invalid Link"}, status_code=404)
|
|
390
|
+
return templates.TemplateResponse(request, "error_page.html", {**error_context, "error_title": "Invalid Link"}, status_code=404)
|
|
391
391
|
|
|
392
392
|
# Load Flock from definition and cache in app.state.shared_flocks
|
|
393
393
|
loaded_flock: Flock | None = None
|
|
@@ -404,7 +404,7 @@ async def page_shared_chat(
|
|
|
404
404
|
except Exception as e_load:
|
|
405
405
|
logger.error(f"Fatal: Could not load Flock from definition for shared chat {share_id}: {e_load}", exc_info=True)
|
|
406
406
|
error_context = get_base_context_web(request, ui_mode="standalone", error=f"Could not load the shared Flock configuration: {e_load!s}")
|
|
407
|
-
return templates.TemplateResponse("error_page.html", {**error_context, "error_title": "Configuration Error"}, status_code=500)
|
|
407
|
+
return templates.TemplateResponse(request, "error_page.html", {**error_context, "error_title": "Configuration Error"}, status_code=500)
|
|
408
408
|
|
|
409
409
|
frozen_chat_cfg = ChatConfig(
|
|
410
410
|
agent_name=shared_config_db.agent_name,
|
|
@@ -426,7 +426,7 @@ async def page_shared_chat(
|
|
|
426
426
|
"flock": loaded_flock # Pass flock for potential display, though backend uses cached one
|
|
427
427
|
})
|
|
428
428
|
|
|
429
|
-
response = templates.TemplateResponse("chat.html", context)
|
|
429
|
+
response = templates.TemplateResponse(request, "chat.html", context)
|
|
430
430
|
if COOKIE_NAME not in request.cookies: # Ensure cookie is set if _ensure_session created a new one
|
|
431
431
|
response.set_cookie(COOKIE_NAME, sid, max_age=60 * 60 * 24 * 7)
|
|
432
432
|
return response
|
|
@@ -436,8 +436,7 @@ async def chat_history_partial_shared(request: Request, share_id: str):
|
|
|
436
436
|
"""HTMX endpoint that returns the rendered message list for a shared chat."""
|
|
437
437
|
# _ensure_session called on page load, so cookie should exist for history keying
|
|
438
438
|
history = _get_history_for_shared_chat(request, share_id)
|
|
439
|
-
return templates.TemplateResponse(
|
|
440
|
-
"partials/_chat_messages.html",
|
|
439
|
+
return templates.TemplateResponse(request, "partials/_chat_messages.html",
|
|
441
440
|
{"request": request, "history": history, "now": datetime.now}
|
|
442
441
|
)
|
|
443
442
|
|
|
@@ -460,8 +459,7 @@ async def chat_send_shared(
|
|
|
460
459
|
# Error response if config or flock couldn't be loaded
|
|
461
460
|
# This history is ephemeral as it won't be saved if the config is bad
|
|
462
461
|
error_history = [{"role": "bot", "text": "Error: Shared chat configuration is invalid or Flock not found.", "timestamp": datetime.now().strftime('%H:%M')}]
|
|
463
|
-
return templates.TemplateResponse(
|
|
464
|
-
"partials/_chat_messages.html",
|
|
462
|
+
return templates.TemplateResponse(request, "partials/_chat_messages.html",
|
|
465
463
|
{"request": request, "history": error_history, "now": datetime.now},
|
|
466
464
|
status_code=404
|
|
467
465
|
)
|
|
@@ -544,8 +542,7 @@ async def chat_send_shared(
|
|
|
544
542
|
"flock_yaml": getattr(flock_inst, 'to_yaml', lambda: "")()
|
|
545
543
|
})
|
|
546
544
|
|
|
547
|
-
return templates.TemplateResponse(
|
|
548
|
-
"partials/_chat_messages.html",
|
|
545
|
+
return templates.TemplateResponse(request, "partials/_chat_messages.html",
|
|
549
546
|
{"request": request, "history": history, "now": datetime.now}
|
|
550
547
|
)
|
|
551
548
|
|