flock-core 0.5.0b21__py3-none-any.whl → 0.5.0b22__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/webapp/app/api/execution.py +232 -24
- flock/webapp/templates/partials/_streaming_results_container.html +195 -0
- {flock_core-0.5.0b21.dist-info → flock_core-0.5.0b22.dist-info}/METADATA +1 -1
- {flock_core-0.5.0b21.dist-info → flock_core-0.5.0b22.dist-info}/RECORD +7 -6
- {flock_core-0.5.0b21.dist-info → flock_core-0.5.0b22.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b21.dist-info → flock_core-0.5.0b22.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b21.dist-info → flock_core-0.5.0b22.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# src/flock/webapp/app/api/execution.py
|
|
2
|
+
import asyncio
|
|
2
3
|
import html
|
|
3
4
|
import json
|
|
5
|
+
import uuid
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from typing import TYPE_CHECKING, Any, Literal
|
|
6
8
|
|
|
@@ -12,7 +14,7 @@ from fastapi import ( # Ensure Form and HTTPException are imported
|
|
|
12
14
|
Request,
|
|
13
15
|
)
|
|
14
16
|
from fastapi.encoders import jsonable_encoder
|
|
15
|
-
from fastapi.responses import FileResponse, HTMLResponse
|
|
17
|
+
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
|
|
16
18
|
from fastapi.templating import Jinja2Templates
|
|
17
19
|
from werkzeug.utils import secure_filename
|
|
18
20
|
|
|
@@ -40,10 +42,6 @@ from flock.webapp.app.dependencies import (
|
|
|
40
42
|
)
|
|
41
43
|
|
|
42
44
|
# Service function now takes app_state
|
|
43
|
-
from flock.webapp.app.services.flock_service import (
|
|
44
|
-
run_current_flock_service,
|
|
45
|
-
# get_current_flock_instance IS NO LONGER IMPORTED
|
|
46
|
-
)
|
|
47
45
|
from flock.webapp.app.services.sharing_store import SharedLinkStoreInterface
|
|
48
46
|
|
|
49
47
|
router = APIRouter()
|
|
@@ -59,6 +57,183 @@ def markdown_filter(text):
|
|
|
59
57
|
templates.env.filters["markdown"] = markdown_filter
|
|
60
58
|
|
|
61
59
|
|
|
60
|
+
class ExecutionStreamManager:
|
|
61
|
+
"""In-memory tracker for live streaming sessions."""
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
self._sessions: dict[str, asyncio.Queue] = {}
|
|
65
|
+
self._lock = asyncio.Lock()
|
|
66
|
+
|
|
67
|
+
async def create_session(self) -> tuple[str, asyncio.Queue]:
|
|
68
|
+
run_id = uuid.uuid4().hex
|
|
69
|
+
queue: asyncio.Queue = asyncio.Queue()
|
|
70
|
+
async with self._lock:
|
|
71
|
+
self._sessions[run_id] = queue
|
|
72
|
+
return run_id, queue
|
|
73
|
+
|
|
74
|
+
async def get_queue(self, run_id: str) -> asyncio.Queue | None:
|
|
75
|
+
async with self._lock:
|
|
76
|
+
return self._sessions.get(run_id)
|
|
77
|
+
|
|
78
|
+
async def remove_session(self, run_id: str) -> None:
|
|
79
|
+
async with self._lock:
|
|
80
|
+
self._sessions.pop(run_id, None)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
execution_stream_manager = ExecutionStreamManager()
|
|
84
|
+
stream_logger = get_flock_logger("webapp.execution.stream")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def _execute_agent_with_stream(
|
|
88
|
+
run_id: str,
|
|
89
|
+
queue: asyncio.Queue,
|
|
90
|
+
start_agent_name: str,
|
|
91
|
+
inputs: dict[str, Any],
|
|
92
|
+
app_state: Any,
|
|
93
|
+
template_context: dict[str, Any],
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Run the requested agent while forwarding streaming chunks to the UI."""
|
|
96
|
+
|
|
97
|
+
completed = False
|
|
98
|
+
|
|
99
|
+
def emit(payload: dict[str, Any]) -> None:
|
|
100
|
+
try:
|
|
101
|
+
queue.put_nowait(payload)
|
|
102
|
+
except asyncio.QueueFull:
|
|
103
|
+
stream_logger.warning(
|
|
104
|
+
"Dropping streaming payload for run %s due to full queue", run_id
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
async def terminate_with_error(message: str) -> None:
|
|
108
|
+
emit({"type": "error", "message": message})
|
|
109
|
+
await finalize_stream()
|
|
110
|
+
|
|
111
|
+
async def finalize_stream() -> None:
|
|
112
|
+
nonlocal completed
|
|
113
|
+
if not completed:
|
|
114
|
+
emit({"type": "complete"})
|
|
115
|
+
completed = True
|
|
116
|
+
await queue.put(None)
|
|
117
|
+
|
|
118
|
+
current_flock: "Flock | None" = getattr(app_state, "flock_instance", None)
|
|
119
|
+
run_store: RunStore | None = getattr(app_state, "run_store", None)
|
|
120
|
+
|
|
121
|
+
if not current_flock:
|
|
122
|
+
stream_logger.error("Stream run aborted: no flock loaded in app state.")
|
|
123
|
+
await terminate_with_error("No Flock loaded in the application.")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
agent = current_flock.agents.get(start_agent_name)
|
|
127
|
+
if not agent:
|
|
128
|
+
stream_logger.error(
|
|
129
|
+
"Stream run aborted: agent '%s' not found in flock '%s'.",
|
|
130
|
+
start_agent_name,
|
|
131
|
+
current_flock.name,
|
|
132
|
+
)
|
|
133
|
+
await terminate_with_error(
|
|
134
|
+
f"Agent '{html.escape(str(start_agent_name))}' not found."
|
|
135
|
+
)
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
evaluator = getattr(agent, "evaluator", None)
|
|
139
|
+
previous_callbacks: list[Any] | None = None
|
|
140
|
+
original_stream_setting: bool | None = None
|
|
141
|
+
|
|
142
|
+
if evaluator is not None:
|
|
143
|
+
previous_callbacks = list(evaluator.config.stream_callbacks or [])
|
|
144
|
+
original_stream_setting = getattr(evaluator.config, "stream", False)
|
|
145
|
+
|
|
146
|
+
def stream_callback(message: Any) -> None:
|
|
147
|
+
chunk = getattr(message, "chunk", None)
|
|
148
|
+
signature_field = getattr(message, "signature_field_name", None)
|
|
149
|
+
if chunk is None:
|
|
150
|
+
return
|
|
151
|
+
emit(
|
|
152
|
+
{
|
|
153
|
+
"type": "token",
|
|
154
|
+
"chunk": str(chunk),
|
|
155
|
+
"field": signature_field,
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
evaluator.config.stream_callbacks = [
|
|
160
|
+
*previous_callbacks,
|
|
161
|
+
stream_callback,
|
|
162
|
+
]
|
|
163
|
+
if not original_stream_setting:
|
|
164
|
+
evaluator.config.stream = True
|
|
165
|
+
else:
|
|
166
|
+
emit(
|
|
167
|
+
{
|
|
168
|
+
"type": "status",
|
|
169
|
+
"message": "Streaming not available for this agent; results will appear when the run completes.",
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
emit(
|
|
175
|
+
{
|
|
176
|
+
"type": "status",
|
|
177
|
+
"message": f"Running agent '{start_agent_name}'...",
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
result_data = await current_flock.run_async(
|
|
181
|
+
agent=start_agent_name, input=inputs, box_result=False
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if run_store and hasattr(run_store, "add_run_details"):
|
|
185
|
+
run_identifier = (
|
|
186
|
+
result_data.get("run_id", run_id)
|
|
187
|
+
if isinstance(result_data, dict)
|
|
188
|
+
else run_id
|
|
189
|
+
)
|
|
190
|
+
run_store.add_run_details(
|
|
191
|
+
run_id=run_identifier,
|
|
192
|
+
agent_name=start_agent_name,
|
|
193
|
+
inputs=inputs,
|
|
194
|
+
outputs=result_data,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
encoded_result = jsonable_encoder(result_data)
|
|
198
|
+
raw_json = json.dumps(
|
|
199
|
+
encoded_result, indent=2, ensure_ascii=False
|
|
200
|
+
).replace("\\n", "\n")
|
|
201
|
+
|
|
202
|
+
template = templates.get_template("partials/_results_display.html")
|
|
203
|
+
final_html = template.render(
|
|
204
|
+
{
|
|
205
|
+
**template_context,
|
|
206
|
+
"result": result_data,
|
|
207
|
+
"result_raw_json": raw_json,
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
emit(
|
|
212
|
+
{
|
|
213
|
+
"type": "final",
|
|
214
|
+
"html": final_html,
|
|
215
|
+
"result": encoded_result,
|
|
216
|
+
"raw_json": raw_json,
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
except Exception as exc: # pragma: no cover - defensive logging
|
|
220
|
+
stream_logger.error(
|
|
221
|
+
"Streamed execution for agent '%s' failed: %s",
|
|
222
|
+
start_agent_name,
|
|
223
|
+
exc,
|
|
224
|
+
exc_info=True,
|
|
225
|
+
)
|
|
226
|
+
await terminate_with_error(f"An error occurred: {html.escape(str(exc))}")
|
|
227
|
+
return
|
|
228
|
+
finally:
|
|
229
|
+
if evaluator is not None:
|
|
230
|
+
if previous_callbacks is not None:
|
|
231
|
+
evaluator.config.stream_callbacks = previous_callbacks
|
|
232
|
+
if original_stream_setting is not None:
|
|
233
|
+
evaluator.config.stream = original_stream_setting
|
|
234
|
+
await finalize_stream()
|
|
235
|
+
|
|
236
|
+
|
|
62
237
|
@router.get("/htmx/execution-form-content", response_class=HTMLResponse)
|
|
63
238
|
async def htmx_get_execution_form_content(
|
|
64
239
|
request: Request,
|
|
@@ -203,35 +378,68 @@ async def htmx_run_flock(
|
|
|
203
378
|
f"<p class='error'>Error processing inputs for {html.escape(str(start_agent_name))}: {html.escape(str(e_parse))}</p>"
|
|
204
379
|
)
|
|
205
380
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
381
|
+
run_id, queue = await execution_stream_manager.create_session()
|
|
382
|
+
stream_url = str(request.url_for("htmx_stream_run", run_id=run_id))
|
|
383
|
+
root_path = request.scope.get("root_path", "")
|
|
209
384
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
385
|
+
template_context = {
|
|
386
|
+
"request": request,
|
|
387
|
+
"feedback_endpoint": f"{root_path}/ui/api/flock/htmx/feedback",
|
|
388
|
+
"share_id": None,
|
|
389
|
+
"flock_name": current_flock_from_state.name,
|
|
390
|
+
"agent_name": start_agent_name,
|
|
391
|
+
"flock_definition": current_flock_from_state.to_yaml(),
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
asyncio.create_task(
|
|
395
|
+
_execute_agent_with_stream(
|
|
396
|
+
run_id=run_id,
|
|
397
|
+
queue=queue,
|
|
398
|
+
start_agent_name=start_agent_name,
|
|
399
|
+
inputs=inputs,
|
|
400
|
+
app_state=request.app.state,
|
|
401
|
+
template_context=template_context,
|
|
402
|
+
)
|
|
216
403
|
)
|
|
217
|
-
|
|
218
|
-
result_data_raw_json_str = raw_json_for_template.replace("\\n", "\n")
|
|
219
|
-
root_path = request.scope.get("root_path", "")
|
|
404
|
+
|
|
220
405
|
return templates.TemplateResponse(
|
|
221
|
-
"partials/
|
|
406
|
+
"partials/_streaming_results_container.html",
|
|
222
407
|
{
|
|
223
408
|
"request": request,
|
|
224
|
-
"
|
|
225
|
-
"
|
|
226
|
-
"feedback_endpoint": f"{root_path}/ui/api/flock/htmx/feedback",
|
|
227
|
-
"share_id": None,
|
|
228
|
-
"flock_name": current_flock_from_state.name,
|
|
409
|
+
"run_id": run_id,
|
|
410
|
+
"stream_url": stream_url,
|
|
229
411
|
"agent_name": start_agent_name,
|
|
230
|
-
"
|
|
412
|
+
"flock_name": current_flock_from_state.name,
|
|
231
413
|
},
|
|
232
414
|
)
|
|
233
415
|
|
|
234
416
|
|
|
417
|
+
@router.get("/htmx/run-stream/{run_id}")
|
|
418
|
+
async def htmx_stream_run(run_id: str):
|
|
419
|
+
"""Server-Sent Events endpoint streaming live agent output."""
|
|
420
|
+
|
|
421
|
+
queue = await execution_stream_manager.get_queue(run_id)
|
|
422
|
+
if queue is None:
|
|
423
|
+
return HTMLResponse(
|
|
424
|
+
"<p class='error'>Streaming session not found or already closed.</p>",
|
|
425
|
+
status_code=404,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
async def event_generator():
|
|
429
|
+
try:
|
|
430
|
+
while True:
|
|
431
|
+
payload = await queue.get()
|
|
432
|
+
if payload is None:
|
|
433
|
+
yield "event: close\ndata: {}\n\n"
|
|
434
|
+
break
|
|
435
|
+
data = json.dumps(payload, ensure_ascii=False)
|
|
436
|
+
yield f"data: {data}\n\n"
|
|
437
|
+
finally:
|
|
438
|
+
await execution_stream_manager.remove_session(run_id)
|
|
439
|
+
|
|
440
|
+
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
|
441
|
+
|
|
442
|
+
|
|
235
443
|
# --- NEW ENDPOINT FOR SHARED RUNS ---
|
|
236
444
|
@router.post("/htmx/run-shared", response_class=HTMLResponse)
|
|
237
445
|
async def htmx_run_shared_flock(
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<div id="streaming-results-wrapper"
|
|
2
|
+
data-run-id="{{ run_id }}"
|
|
3
|
+
data-stream-url="{{ stream_url }}"
|
|
4
|
+
data-target-id="results-display"
|
|
5
|
+
data-agent-name="{{ agent_name }}">
|
|
6
|
+
<header style="margin-bottom: 0.75rem;">
|
|
7
|
+
<h5 style="margin: 0;">Streaming {{ agent_name }}</h5>
|
|
8
|
+
<p style="margin: 0; color: var(--pico-muted-color);">Live output appears below while the agent runs.</p>
|
|
9
|
+
</header>
|
|
10
|
+
|
|
11
|
+
<p class="error" data-role="error" hidden></p>
|
|
12
|
+
<div class="stream-output" data-role="output" style="min-height: 8rem; white-space: normal; word-break: break-word; font-family: var(--pico-code-font-family, monospace);">Connecting to agent…</div>
|
|
13
|
+
<div data-role="progress" style="margin-top: 0.5rem;" role="status">
|
|
14
|
+
<progress indeterminate></progress> Streaming response…
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
(function () {
|
|
20
|
+
const script = document.currentScript;
|
|
21
|
+
if (!script) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const wrapper = script.previousElementSibling;
|
|
25
|
+
if (!(wrapper instanceof HTMLElement)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (wrapper.dataset.streamInit === '1') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
wrapper.dataset.streamInit = '1';
|
|
32
|
+
|
|
33
|
+
const streamUrl = wrapper.dataset.streamUrl;
|
|
34
|
+
const runId = wrapper.dataset.runId;
|
|
35
|
+
const targetId = wrapper.dataset.targetId || 'results-display';
|
|
36
|
+
|
|
37
|
+
const outputEl = wrapper.querySelector('[data-role="output"]');
|
|
38
|
+
const errorEl = wrapper.querySelector('[data-role="error"]');
|
|
39
|
+
const progressEl = wrapper.querySelector('[data-role="progress"]');
|
|
40
|
+
|
|
41
|
+
if (!streamUrl || !runId || !(outputEl instanceof HTMLElement)) {
|
|
42
|
+
if (errorEl instanceof HTMLElement) {
|
|
43
|
+
errorEl.textContent = 'Streaming setup failed due to missing metadata.';
|
|
44
|
+
errorEl.hidden = false;
|
|
45
|
+
}
|
|
46
|
+
if (progressEl instanceof HTMLElement) {
|
|
47
|
+
progressEl.hidden = true;
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let plainText = outputEl instanceof HTMLElement ? outputEl.textContent || '' : '';
|
|
53
|
+
let source;
|
|
54
|
+
const fieldValues = new Map();
|
|
55
|
+
let latestStatus = '';
|
|
56
|
+
|
|
57
|
+
function escapeHtml(value) {
|
|
58
|
+
return value.replace(/[&<>"']/g, (char) => ({
|
|
59
|
+
'&': '&',
|
|
60
|
+
'<': '<',
|
|
61
|
+
'>': '>',
|
|
62
|
+
'"': '"',
|
|
63
|
+
"'": ''',
|
|
64
|
+
})[char]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function renderTableCell(value) {
|
|
68
|
+
return escapeHtml(value).replace(/\n/g, '<br>');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function renderStream() {
|
|
72
|
+
if (!(outputEl instanceof HTMLElement)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (fieldValues.size > 0) {
|
|
76
|
+
outputEl.style.whiteSpace = 'normal';
|
|
77
|
+
let rows = '';
|
|
78
|
+
fieldValues.forEach((value, field) => {
|
|
79
|
+
rows += `<tr><td class="stream-field" style="white-space: nowrap; padding-right: 1rem; vertical-align: top;">${escapeHtml(field)}</td><td>${renderTableCell(value)}</td></tr>`;
|
|
80
|
+
});
|
|
81
|
+
if (latestStatus) {
|
|
82
|
+
rows += `<tr class="stream-status"><td class="stream-field" style="white-space: nowrap; padding-right: 1rem; vertical-align: top;">status</td><td>${escapeHtml(latestStatus)}</td></tr>`;
|
|
83
|
+
}
|
|
84
|
+
outputEl.innerHTML = `<table class="structured-table streaming-table" style="width:100%; border-collapse: collapse; table-layout: auto;"><tbody>${rows}</tbody></table>`;
|
|
85
|
+
} else {
|
|
86
|
+
outputEl.style.whiteSpace = 'pre-wrap';
|
|
87
|
+
outputEl.textContent = plainText;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function showError(message) {
|
|
92
|
+
if (errorEl instanceof HTMLElement) {
|
|
93
|
+
errorEl.textContent = message;
|
|
94
|
+
errorEl.hidden = false;
|
|
95
|
+
}
|
|
96
|
+
if (progressEl instanceof HTMLElement) {
|
|
97
|
+
progressEl.hidden = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function closeStream() {
|
|
102
|
+
if (source) {
|
|
103
|
+
source.close();
|
|
104
|
+
source = undefined;
|
|
105
|
+
}
|
|
106
|
+
if (progressEl instanceof HTMLElement) {
|
|
107
|
+
progressEl.hidden = true;
|
|
108
|
+
}
|
|
109
|
+
renderStream();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function renderFinal(html, rawJson) {
|
|
113
|
+
const target = document.getElementById(targetId);
|
|
114
|
+
if (!target) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
target.innerHTML = html;
|
|
118
|
+
if (window.htmx) {
|
|
119
|
+
window.htmx.process(target);
|
|
120
|
+
}
|
|
121
|
+
if (window.Prism) {
|
|
122
|
+
window.Prism.highlightAllUnder(target);
|
|
123
|
+
}
|
|
124
|
+
if (outputEl instanceof HTMLElement) {
|
|
125
|
+
outputEl.textContent = '';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
source = new EventSource(streamUrl);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error('Failed to start EventSource', err);
|
|
133
|
+
showError('Failed to connect for streaming.');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
source.onmessage = (event) => {
|
|
138
|
+
if (!event.data) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let payload;
|
|
142
|
+
try {
|
|
143
|
+
payload = JSON.parse(event.data);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.warn('Unable to parse streaming payload', err);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
switch (payload.type) {
|
|
150
|
+
case 'token':
|
|
151
|
+
if (typeof payload.chunk === 'string') {
|
|
152
|
+
if (payload.field) {
|
|
153
|
+
const existing = fieldValues.get(payload.field) || '';
|
|
154
|
+
fieldValues.set(payload.field, existing + payload.chunk);
|
|
155
|
+
} else {
|
|
156
|
+
plainText = (plainText === 'Connecting to agent…' ? '' : plainText) + payload.chunk;
|
|
157
|
+
}
|
|
158
|
+
renderStream();
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
case 'status':
|
|
162
|
+
if (payload.message) {
|
|
163
|
+
latestStatus = payload.message;
|
|
164
|
+
if (fieldValues.size === 0) {
|
|
165
|
+
plainText = (plainText === 'Connecting to agent…' ? '' : plainText);
|
|
166
|
+
if (plainText && !plainText.endsWith('\n')) {
|
|
167
|
+
plainText += '\n';
|
|
168
|
+
}
|
|
169
|
+
plainText += payload.message + '\n';
|
|
170
|
+
}
|
|
171
|
+
renderStream();
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case 'error':
|
|
175
|
+
showError(payload.message || 'An unexpected error occurred while streaming.');
|
|
176
|
+
closeStream();
|
|
177
|
+
break;
|
|
178
|
+
case 'final':
|
|
179
|
+
closeStream();
|
|
180
|
+
renderFinal(payload.html || '', payload.raw_json || payload.rawJson);
|
|
181
|
+
break;
|
|
182
|
+
case 'complete':
|
|
183
|
+
closeStream();
|
|
184
|
+
break;
|
|
185
|
+
default:
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
source.onerror = () => {
|
|
191
|
+
showError('Connection lost while streaming.');
|
|
192
|
+
closeStream();
|
|
193
|
+
};
|
|
194
|
+
})();
|
|
195
|
+
</script>
|
|
@@ -490,7 +490,7 @@ flock/webapp/app/theme_mapper.py,sha256=QzWwLWpED78oYp3FjZ9zxv1KxCyj43m8MZ0fhfzz
|
|
|
490
490
|
flock/webapp/app/utils.py,sha256=RF8DMKKAj1XPmm4txUdo2OdswI1ATQ7cqUm6G9JFDzA,2942
|
|
491
491
|
flock/webapp/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
492
492
|
flock/webapp/app/api/agent_management.py,sha256=5xqO94QjjAYvxImyjKV9EGUQOvo4n3eqs7pGwGPSQJ4,10394
|
|
493
|
-
flock/webapp/app/api/execution.py,sha256=
|
|
493
|
+
flock/webapp/app/api/execution.py,sha256=D-gqeF0JRZ_9TZTuf1-7gLIPL-p8F9Qp66CjVgJKSlw,24758
|
|
494
494
|
flock/webapp/app/api/flock_management.py,sha256=1o-6-36kTnUjI3am_BqLpdrcz0aqFXrxE-hQHIFcCsg,4869
|
|
495
495
|
flock/webapp/app/api/registry_viewer.py,sha256=IoInxJiRR0yFlecG_l2_eRc6l35RQQyEDMG9BcBkipY,1020
|
|
496
496
|
flock/webapp/app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -542,6 +542,7 @@ flock/webapp/templates/partials/_settings_view.html,sha256=f2h9jnDv8-JRkDzsbk_1o
|
|
|
542
542
|
flock/webapp/templates/partials/_share_chat_link_snippet.html,sha256=N83lNAbkZiDfzZYviKwURPGGErSZhRlxnNzUqXsB7lE,793
|
|
543
543
|
flock/webapp/templates/partials/_share_link_snippet.html,sha256=6en9lOdtu8FwVbtmkJzSQpHQ1WFXHnCbe84FDgAEF3U,1533
|
|
544
544
|
flock/webapp/templates/partials/_sidebar.html,sha256=yfhEcF3xKI5j1c3iq46mU8mmPvgyvCHXe6xT7vsE6KM,4984
|
|
545
|
+
flock/webapp/templates/partials/_streaming_results_container.html,sha256=WVp_IafF1_4puyfs3ueIJ16ehZHiMDBADUXRoZJ1_Yo,6684
|
|
545
546
|
flock/webapp/templates/partials/_structured_data_view.html,sha256=TEaXcMGba9ruxEc_MLxygIO1qWcuSTo1FnosFtGSKWI,2101
|
|
546
547
|
flock/webapp/templates/partials/_theme_preview.html,sha256=THeMYTXzgzHJxzWqaTtUhmJyBZT3saLRAa6wzZa4qnk,1347
|
|
547
548
|
flock/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -551,8 +552,8 @@ flock/workflow/agent_execution_activity.py,sha256=0exwmeWKYXXxdUqDf4YaUVpn0zl06S
|
|
|
551
552
|
flock/workflow/flock_workflow.py,sha256=sKFsRIL_bDGonXSNhK1zwu6UechghC_PihJJMidI-VI,9139
|
|
552
553
|
flock/workflow/temporal_config.py,sha256=3_8O7SDEjMsSMXsWJBfnb6XTp0TFaz39uyzSlMTSF_I,3988
|
|
553
554
|
flock/workflow/temporal_setup.py,sha256=KR6MlWOrpMtv8NyhaIPAsfl4tjobt81OBByQvg8Kw-Y,1948
|
|
554
|
-
flock_core-0.5.
|
|
555
|
-
flock_core-0.5.
|
|
556
|
-
flock_core-0.5.
|
|
557
|
-
flock_core-0.5.
|
|
558
|
-
flock_core-0.5.
|
|
555
|
+
flock_core-0.5.0b22.dist-info/METADATA,sha256=OQrcZ9gIQRqbbhfU0RSIbo7auCybTmemyee39C6XWHc,9997
|
|
556
|
+
flock_core-0.5.0b22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
557
|
+
flock_core-0.5.0b22.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
|
|
558
|
+
flock_core-0.5.0b22.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
|
|
559
|
+
flock_core-0.5.0b22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|