flock-core 0.4.527__py3-none-any.whl → 0.4.529__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.

@@ -1,10 +1,9 @@
1
1
  # src/flock/webapp/app/api/execution.py
2
+ import html
2
3
  import json
3
- from datetime import datetime
4
4
  from pathlib import Path
5
- from typing import TYPE_CHECKING, Any
5
+ from typing import TYPE_CHECKING, Any, Literal
6
6
 
7
- import html
8
7
  import markdown2 # Import markdown2
9
8
  from fastapi import ( # Ensure Form and HTTPException are imported
10
9
  APIRouter,
@@ -15,6 +14,14 @@ from fastapi import ( # Ensure Form and HTTPException are imported
15
14
  from fastapi.encoders import jsonable_encoder
16
15
  from fastapi.responses import FileResponse, HTMLResponse
17
16
  from fastapi.templating import Jinja2Templates
17
+ from werkzeug.utils import secure_filename
18
+
19
+ from flock.webapp.app.services.feedback_file_service import (
20
+ create_csv_feedback_file,
21
+ create_csv_feedback_file_for_agent,
22
+ create_xlsx_feedback_file,
23
+ create_xlsx_feedback_file_for_agent,
24
+ )
18
25
 
19
26
  if TYPE_CHECKING:
20
27
  from flock.core.flock import Flock
@@ -385,6 +392,8 @@ async def htmx_submit_feedback_shared(
385
392
  expected_response: str | None = Form(None),
386
393
  actual_response: str | None = Form(None),
387
394
  flock_definition: str | None = Form(None),
395
+ flock_name: str | None = Form(None),
396
+ agent_name: str | None = Form(None),
388
397
  store: SharedLinkStoreInterface = Depends(get_shared_link_store),
389
398
  ):
390
399
  from uuid import uuid4
@@ -399,6 +408,8 @@ async def htmx_submit_feedback_shared(
399
408
  expected_response=expected_response,
400
409
  actual_response=actual_response,
401
410
  flock_definition=flock_definition,
411
+ agent_name=agent_name,
412
+ flock_name=flock_name,
402
413
  )
403
414
  await store.save_feedback(record)
404
415
  return HTMLResponse(
@@ -406,9 +417,10 @@ async def htmx_submit_feedback_shared(
406
417
  )
407
418
 
408
419
 
409
- @router.get("/htmx/feedback-download/", response_class=FileResponse)
420
+ @router.get("/htmx/feedback-download/{format}", response_class=FileResponse)
410
421
  async def chat_feedback_download_all(
411
422
  request: Request,
423
+ format: Literal["csv", "xlsx"] = "csv",
412
424
  store: SharedLinkStoreInterface = Depends(get_shared_link_store),
413
425
  ):
414
426
  """Download all feedback records for all agents in the current flock as a CSV file.
@@ -421,170 +433,69 @@ async def chat_feedback_download_all(
421
433
  store: The shared link store interface dependency
422
434
 
423
435
  Returns:
424
- FileResponse: CSV file containing all feedback records for all agents
436
+ FileResponse: CSV/XLSX file containing all feedback records for all agents
425
437
 
426
438
  Raises:
427
439
  HTTPException: If no flock is loaded or no agents are found in the flock
428
440
  """
429
- import csv
430
- import tempfile
431
- from pathlib import Path
432
-
433
- # Get the current flock instance to retrieve all agent names
434
- from flock.core.flock import Flock
435
-
436
- current_flock_instance: Flock | None = getattr(
437
- request.app.state, "flock_instance", None
438
- )
439
-
440
- if not current_flock_instance:
441
- # If no flock is loaded, return an error response
442
- from fastapi import HTTPException
443
-
444
- raise HTTPException(
445
- status_code=400, detail="No Flock loaded to download feedback for"
441
+ safe_format = secure_filename(format)
442
+ if safe_format == "csv":
443
+ return await create_csv_feedback_file(
444
+ request=request,
445
+ store=store,
446
+ separator=","
446
447
  )
447
-
448
- # Get all agent names from the current flock
449
- all_agent_names = list(current_flock_instance.agents.keys())
450
-
451
- if not all_agent_names:
452
- # If no agents in the flock, return an error response
448
+ elif safe_format == "xlsx":
449
+ return await create_xlsx_feedback_file(
450
+ request=request,
451
+ store=store,
452
+ )
453
+ else:
453
454
  from fastapi import HTTPException
454
455
 
455
456
  raise HTTPException(
456
- status_code=400, detail="No agents found in the current Flock"
457
+ status_code=400,
458
+ detail="Invalid file-format specified. Valid formats are: 'csv', 'xlsx'"
457
459
  )
458
460
 
459
- # Collect all feedback records from all agents
460
- all_records = []
461
- for agent_name in all_agent_names:
462
- records = await store.get_all_feedback_records_for_agent(
463
- agent_name=agent_name
464
- )
465
- all_records.extend(records)
466
-
467
- # Create a temporary CSV file
468
- temp_dir = tempfile.gettempdir()
469
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
470
- csv_filename = f"flock_feedback_all_agents_{timestamp}.csv"
471
- csv_path = Path(temp_dir) / csv_filename
472
-
473
- # Define CSV headers based on FeedbackRecord fields
474
- headers = [
475
- "feedback_id",
476
- "share_id",
477
- "context_type",
478
- "reason",
479
- "expected_response",
480
- "actual_response",
481
- "created_at",
482
- "flock_name",
483
- "agent_name",
484
- "flock_definition",
485
- ]
486
-
487
- # Write records to CSV
488
- with open(csv_path, "w", newline="", encoding="utf-8") as csvfile:
489
- writer = csv.DictWriter(csvfile, fieldnames=headers)
490
- writer.writeheader()
491
-
492
- for record in all_records:
493
- # Convert the Pydantic model to dict and ensure all fields are present
494
- row_data = {}
495
- for header in headers:
496
- value = getattr(record, header, None)
497
- # Convert datetime to ISO string for CSV
498
- if header == "created_at" and value:
499
- row_data[header] = (
500
- value.isoformat()
501
- if isinstance(value, datetime)
502
- else str(value)
503
- )
504
- else:
505
- row_data[header] = str(value) if value is not None else ""
506
- writer.writerow(row_data)
507
-
508
- # Return FileResponse with the CSV file
509
- return FileResponse(
510
- path=str(csv_path),
511
- filename=csv_filename,
512
- media_type="text/csv",
513
- headers={"Content-Disposition": f"attachment; filename={csv_filename}"},
514
- )
515
461
 
516
-
517
- @router.get("/htmx/feedback-download/{agent_name}", response_class=FileResponse)
462
+ @router.get("/htmx/feedback-download/{agent_name}/{format}", response_class=FileResponse)
518
463
  async def chat_feedback_download(
519
464
  request: Request,
520
465
  agent_name: str,
466
+ format: Literal["csv", "xlsx"] = "csv",
521
467
  store: SharedLinkStoreInterface = Depends(get_shared_link_store),
522
468
  ):
523
- """Download all feedback records for a specific agent as a CSV file.
469
+ """Download all feedback records for a specific agent as a file.
524
470
 
525
471
  Args:
526
472
  request: The FastAPI request object
527
473
  agent_name: Name of the agent to download feedback for
528
474
  store: The shared link store interface dependency
475
+ format: Either 'csv' or 'xlsx' the file format to use
529
476
 
530
477
  Returns:
531
- FileResponse: CSV file containing all feedback records for the specified agent
478
+ FileResponse: CSV/XLSX file containing all feedback records for the specified agent
532
479
  """
533
- import csv
534
- import tempfile
535
- from pathlib import Path
536
- from werkzeug.utils import secure_filename
537
-
538
- records = await store.get_all_feedback_records_for_agent(
539
- agent_name=agent_name
540
- )
541
-
542
- # Create a temporary CSV file
543
- temp_dir = tempfile.gettempdir()
544
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
480
+ safe_format = secure_filename(format)
545
481
  safe_agent_name = secure_filename(agent_name)
546
- csv_filename = f"flock_feedback_{safe_agent_name}_{timestamp}.csv"
547
- csv_path = Path(temp_dir) / csv_filename
548
-
549
- # Define CSV headers based on FeedbackRecord fields
550
- headers = [
551
- "feedback_id",
552
- "share_id",
553
- "context_type",
554
- "reason",
555
- "expected_response",
556
- "actual_response",
557
- "created_at",
558
- "flock_name",
559
- "agent_name",
560
- "flock_definition",
561
- ]
562
-
563
- # Write records to CSV
564
- with open(csv_path, "w", newline="", encoding="utf-8") as csvfile:
565
- writer = csv.DictWriter(csvfile, fieldnames=headers)
566
- writer.writeheader()
567
-
568
- for record in records:
569
- # Convert the Pydantic model to dict and ensure all fields are present
570
- row_data = {}
571
- for header in headers:
572
- value = getattr(record, header, None)
573
- # Convert datetime to ISO string for CSV
574
- if header == "created_at" and value:
575
- row_data[header] = (
576
- value.isoformat()
577
- if isinstance(value, datetime)
578
- else str(value)
579
- )
580
- else:
581
- row_data[header] = str(value) if value is not None else ""
582
- writer.writerow(row_data)
583
-
584
- # Return FileResponse with the CSV file
585
- return FileResponse(
586
- path=str(csv_path),
587
- filename=csv_filename,
588
- media_type="text/csv",
589
- headers={"Content-Disposition": f"attachment; filename={csv_filename}"},
590
- )
482
+ if safe_format == "csv":
483
+ return await create_csv_feedback_file_for_agent(
484
+ request=request,
485
+ store=store,
486
+ separator=",",
487
+ agent_name=safe_agent_name,
488
+ )
489
+ elif safe_format == "xlsx":
490
+ return await create_xlsx_feedback_file_for_agent(
491
+ request=request,
492
+ store=store,
493
+ agent_name=safe_agent_name,
494
+ )
495
+ else:
496
+ from fastapi import HTTPException
497
+
498
+ raise HTTPException(
499
+ status_code=400,
500
+ detail="Invalid file-format specified. Valid formats are: 'csv', 'xlsx'"
501
+ )
flock/webapp/app/chat.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import ast # Add import ast
4
4
  import json
5
5
  from datetime import datetime
6
+ from typing import Literal
6
7
  from uuid import uuid4
7
8
 
8
9
  import markdown2 # Added for Markdown to HTML conversion
@@ -14,6 +15,12 @@ from flock.core.flock import Flock
14
15
  from flock.core.logging.logging import get_logger
15
16
  from flock.webapp.app.dependencies import get_shared_link_store
16
17
  from flock.webapp.app.main import get_base_context_web, templates
18
+ from flock.webapp.app.services.feedback_file_service import (
19
+ create_csv_feedback_file,
20
+ create_csv_feedback_file_for_agent,
21
+ create_xlsx_feedback_file,
22
+ create_xlsx_feedback_file_for_agent,
23
+ )
17
24
  from flock.webapp.app.services.sharing_models import (
18
25
  FeedbackRecord,
19
26
  SharedLinkConfig,
@@ -602,143 +609,57 @@ async def chat_feedback_shared(request: Request,
602
609
  headers = {"HX-Trigger": json.dumps(toast_event)}
603
610
  return Response(status_code=204, headers=headers)
604
611
 
605
- @router.get("/chat/htmx/feedback-download/", response_class=FileResponse, include_in_schema=False)
612
+ @router.get("/chat/htmx/feedback-download/{format}", response_class=FileResponse, include_in_schema=False)
606
613
  async def chat_feedback_download_all(
607
614
  request: Request,
615
+ format: Literal["csv", "xlsx"] = "csv",
608
616
  store: SharedLinkStoreInterface = Depends(get_shared_link_store)
609
617
  ):
610
618
  """Download all feedback records for all agents."""
611
- import csv
612
- import tempfile
613
- from pathlib import Path
614
-
615
-
616
- current_flock_instance: Flock | None = getattr(
617
- request.app.state, "flock_instance", None
618
- )
619
-
620
- if not current_flock_instance:
621
- from fastapi import HTTPException
622
-
623
- raise HTTPException(
624
- status_code=400, detail="No Flock loaded to download feedback for"
619
+ if format == "csv":
620
+ return await create_csv_feedback_file(
621
+ request=request,
622
+ store=store,
623
+ separator=","
625
624
  )
626
-
627
- all_agent_names = list(current_flock_instance.agents.keys)
628
-
629
- if not all_agent_names:
625
+ elif format == "xlsx":
626
+ return await create_xlsx_feedback_file(
627
+ request=request,
628
+ store=store,
629
+ )
630
+ else:
630
631
  from fastapi import HTTPException
631
632
 
632
633
  raise HTTPException(
633
634
  status_code=400,
634
- detail="No agents found in the current Flock"
635
- )
636
-
637
- all_records = []
638
-
639
- for agent_name in all_agent_names:
640
- records = await store.get_all_feedback_records_for_agent(
641
- agent_name=agent_name
635
+ detail="Invalid file-format specified. Valid formats are: 'csv', 'xlsx'"
642
636
  )
643
637
 
644
- all_records.extend(records)
645
-
646
- temp_dir = tempfile.gettempdir()
647
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
648
- csv_filename = f"flock_feedback_all_agents_{timestamp}.csv"
649
- csv_path = Path(temp_dir) / csv_filename
650
-
651
- headers = [
652
- "feedback_id",
653
- "share_id",
654
- "context_type",
655
- "reason",
656
- "excpected_response",
657
- "actual_response",
658
- "created_at",
659
- "flock_name",
660
- "agent_name",
661
- "flock_definition"
662
- ]
663
-
664
- with open(csv_path, "w", newline="", encoding="utf-8") as csvfile:
665
- writer = csv.DictWriter(csvfile, fieldnames=headers)
666
- writer.writeheader()
667
-
668
- for record in all_records:
669
- row_data = {}
670
- for header in headers:
671
- value = getattr(record, header, None)
672
- if header == "created_at" and value:
673
- row_data[header] = (
674
- value.isoformat()
675
- if isinstance(value, datetime)
676
- else str(value)
677
- )
678
- else:
679
- row_data[header] = str(value) if value is not None else ""
680
- writer.writerow(row_data)
681
-
682
- return FileResponse(
683
- path=str(csv_path),
684
- filename=csv_filename,
685
- media_type="text/csv",
686
- headers={"Content-Disposition": f"attachment; filename={csv_filename}"}
687
- )
688
-
689
- @router.get("/chat/htmx/feedback-download/{agent_name}", response_class=FileResponse, include_in_schema=False)
638
+ @router.get("/chat/htmx/feedback-download/{agent_name}/{format}", response_class=FileResponse, include_in_schema=False)
690
639
  async def chat_feedback_download(
691
640
  request: Request,
692
641
  agent_name: str,
642
+ format: Literal["csv", "xlsx"] = "csv",
693
643
  store: SharedLinkStoreInterface = Depends(get_shared_link_store)
694
644
  ):
695
- import csv
696
- import tempfile
697
- from pathlib import Path
698
-
699
- records = await store.get_all_feedback_records_for_agent(agent_name=agent_name)
700
-
701
- # Create a temporary CSV file
702
- temp_dir = tempfile.gettempdir()
703
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
704
- csv_filename = f"flock_feedback_{timestamp}.csv"
705
- csv_path = Path(temp_dir) / csv_filename
706
-
707
- # Define CSV headers based on FeedbackRecord fields
708
- headers = [
709
- "feedback_id",
710
- "share_id",
711
- "context_type",
712
- "reason",
713
- "expected_response",
714
- "actual_response",
715
- "created_at",
716
- "flock_name",
717
- "agent_name",
718
- "flock_definition"
719
- ]
720
-
721
- # Write records to CSV
722
- with open(csv_path, 'w', newline='', encoding='utf-8') as csvfile:
723
- writer = csv.DictWriter(csvfile, fieldnames=headers)
724
- writer.writeheader()
725
-
726
- for record in records:
727
- # Convert the Pydantic model to dict and ensure all fields are present
728
- row_data = {}
729
- for header in headers:
730
- value = getattr(record, header, None)
731
- # Convert datetime to ISO string for CSV
732
- if header == "created_at" and value:
733
- row_data[header] = value.isoformat() if isinstance(value, datetime) else str(value)
734
- else:
735
- row_data[header] = str(value) if value is not None else ""
736
- writer.writerow(row_data)
737
-
738
- # Return FileResponse with the CSV file
739
- return FileResponse(
740
- path=str(csv_path),
741
- filename=csv_filename,
742
- media_type="text/csv",
743
- headers={"Content-Disposition": f"attachment; filename={csv_filename}"}
744
- )
645
+ """Download all feedback records for a specific agent as a File."""
646
+ if format == "csv":
647
+ return await create_csv_feedback_file_for_agent(
648
+ request=request,
649
+ store=store,
650
+ separator=",",
651
+ agent_name=agent_name,
652
+ )
653
+ elif format == "xlsx":
654
+ return await create_xlsx_feedback_file_for_agent(
655
+ request=request,
656
+ store=store,
657
+ agent_name=agent_name,
658
+ )
659
+ else:
660
+ from fastapi import HTTPException
661
+
662
+ raise HTTPException(
663
+ status_code=400,
664
+ detail="Invalid file-format specified. Valid formats are: 'csv', 'xlsx'"
665
+ )
@@ -0,0 +1,363 @@
1
+ """Methods for creating Feedback-File Downloads."""
2
+ import csv
3
+ import tempfile
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from werkzeug.utils import secure_filename
7
+ from fastapi import Request
8
+ from fastapi.responses import FileResponse
9
+
10
+ from flock.webapp.app.services.sharing_store import SharedLinkStoreInterface
11
+
12
+
13
+ async def create_xlsx_feedback_file_for_agent(
14
+ request: Request,
15
+ store: SharedLinkStoreInterface,
16
+ agent_name: str,
17
+ ) -> FileResponse:
18
+ """Creates an XLSX-File containing all feedback-entries for a single agent."""
19
+ from flock.core.flock import Flock
20
+
21
+ current_flock_instance: Flock | None = getattr(
22
+ request.app.state, "flock_instance", None
23
+ )
24
+
25
+ if not current_flock_instance:
26
+ from fastapi import HTTPException
27
+
28
+ raise HTTPException(
29
+ status_code=400,
30
+ detail="No Flock loaded to download feedback for"
31
+ )
32
+
33
+ all_agent_names = list(current_flock_instance.agents.keys())
34
+
35
+ if not all_agent_names:
36
+ from fastapi import HTTPException
37
+
38
+ raise HTTPException(
39
+ status_code=400,
40
+ detail="No agents found in the current Flock"
41
+ )
42
+
43
+ all_records = await store.get_all_feedback_records_for_agent(
44
+ agent_name=agent_name
45
+ )
46
+
47
+ temp_dir = tempfile.gettempdir()
48
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
49
+ safe_agent_name = secure_filename(agent_name)
50
+ xlsx_filename = f"flock_feedback_{safe_agent_name}_{timestamp}.xlsx"
51
+ xlsx_path = Path(temp_dir) / xlsx_filename
52
+ headers = [
53
+ "feedback_id",
54
+ "share_id",
55
+ "context_type",
56
+ "reason",
57
+ "expected_response",
58
+ "actual_response",
59
+ "created_at",
60
+ "flock_name",
61
+ "agent_name",
62
+ "flock_definition",
63
+ ]
64
+
65
+ return await _write_xlsx_file(
66
+ records=all_records,
67
+ path=xlsx_path,
68
+ filename=xlsx_filename,
69
+ headers=headers,
70
+ )
71
+
72
+ async def create_xlsx_feedback_file(
73
+ request: Request,
74
+ store: SharedLinkStoreInterface
75
+ ) -> FileResponse:
76
+ """Creates an XLSX-File containing all feddback-entries for all agents."""
77
+ from flock.core.flock import Flock
78
+
79
+ current_flock_instance: Flock | None = getattr(
80
+ request.app.state, "flock_instance", None
81
+ )
82
+
83
+ if not current_flock_instance:
84
+ # If no flock is loaded, return an error response
85
+ from fastapi import HTTPException
86
+
87
+ raise HTTPException(
88
+ status_code=400,
89
+ detail="No Flock loaded to download feedback for"
90
+ )
91
+
92
+ # Get all agent names from the current flock
93
+ all_agent_names = list(current_flock_instance.agents.keys())
94
+
95
+ if not all_agent_names:
96
+
97
+ from fastapi import HTTPException
98
+
99
+ raise HTTPException(
100
+ status_code=400,
101
+ detail="No agents found in the current Flock"
102
+ )
103
+
104
+ all_records = []
105
+ for agent_name in all_agent_names:
106
+ agent_records = await store.get_all_feedback_records_for_agent(
107
+ agent_name=agent_name,
108
+ )
109
+ all_records.extend(agent_records)
110
+
111
+ temp_dir = tempfile.gettempdir()
112
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
113
+ xlsx_filename = f"flock_feedback_all_agents_{timestamp}.xlsx"
114
+ xlsx_path = Path(temp_dir) / xlsx_filename
115
+ headers = [
116
+ "feedback_id",
117
+ "share_id",
118
+ "context_type",
119
+ "reason",
120
+ "expected_response",
121
+ "actual_response",
122
+ "created_at",
123
+ "flock_name",
124
+ "agent_name",
125
+ "flock_definition",
126
+ ]
127
+
128
+ return await _write_xlsx_file(
129
+ records=all_records,
130
+ path=xlsx_path,
131
+ filename=xlsx_filename,
132
+ headers=headers,
133
+ )
134
+
135
+
136
+ async def create_csv_feedback_file_for_agent(
137
+ request: Request,
138
+ store: SharedLinkStoreInterface,
139
+ agent_name: str,
140
+ separator: str = ",",
141
+ ) -> FileResponse:
142
+ """Creates a CSV-File filled with the feedback-records for a single agent."""
143
+ from flock.core.flock import Flock
144
+
145
+ current_flock_instance: Flock | None = getattr(
146
+ request.app.state, "flock_instance", None
147
+ )
148
+
149
+ if not current_flock_instance:
150
+ # If no flock is loaded, return an error response
151
+ from fastapi import HTTPException
152
+
153
+ raise HTTPException(
154
+ status_code=400,
155
+ detail="No Flock loaded to download feedback for"
156
+ )
157
+
158
+
159
+ # Get all agent names from the current flock
160
+ all_agent_names = list(current_flock_instance.agents.keys())
161
+
162
+
163
+ if not all_agent_names:
164
+
165
+ from fastapi import HTTPException
166
+
167
+ raise HTTPException(
168
+ status_code=400,
169
+ detail="No agents found in the current Flock"
170
+ )
171
+
172
+ all_records = await store.get_all_feedback_records_for_agent(
173
+ agent_name=agent_name
174
+ )
175
+ temp_dir = tempfile.gettempdir()
176
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
177
+ safe_agent_name = secure_filename(agent_name)
178
+ csv_filename = f"flock_feedback_{safe_agent_name}_{timestamp}.csv"
179
+ csv_path = Path(temp_dir) / csv_filename
180
+ headers = [
181
+ "feedback_id",
182
+ "share_id",
183
+ "context_type",
184
+ "reason",
185
+ "expected_response",
186
+ "actual_response",
187
+ "created_at",
188
+ "flock_name",
189
+ "agent_name",
190
+ "flock_definition",
191
+ ]
192
+ return await _write_csv_file(
193
+ records=all_records,
194
+ path=csv_path,
195
+ headers=headers,
196
+ separator=separator,
197
+ filename=csv_filename,
198
+ )
199
+
200
+
201
+ async def create_csv_feedback_file(
202
+ request: Request,
203
+ store: SharedLinkStoreInterface,
204
+ separator: str = ",",
205
+
206
+ ) -> FileResponse:
207
+ """Creates a CSV-File filled with the feedback-records for all agents."""
208
+ from flock.core.flock import Flock
209
+
210
+ current_flock_instance: Flock | None = getattr(
211
+ request.app.state, "flock_instance", None
212
+ )
213
+
214
+ if not current_flock_instance:
215
+ # If no flock is loaded, return an error response
216
+ from fastapi import HTTPException
217
+
218
+ raise HTTPException(
219
+ status_code=400,
220
+ detail="No Flock loaded to download feedback for"
221
+ )
222
+
223
+ # Get all agent names from the current flock
224
+ all_agent_names = list(current_flock_instance.agents.keys())
225
+
226
+ if not all_agent_names:
227
+
228
+ from fastapi import HTTPException
229
+
230
+ raise HTTPException(
231
+ status_code=400,
232
+ detail="No agents found in the current Flock"
233
+ )
234
+
235
+ all_records = []
236
+ for agent_name in all_agent_names:
237
+ records_for_agent = await store.get_all_feedback_records_for_agent(
238
+ agent_name=agent_name
239
+ )
240
+ all_records.extend(records_for_agent)
241
+
242
+ temp_dir = tempfile.gettempdir()
243
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
244
+ csv_filename = f"flock_feedback_all_agents_{timestamp}.csv"
245
+ csv_path = Path(temp_dir) / csv_filename
246
+ headers = [
247
+ "feedback_id",
248
+ "share_id",
249
+ "context_type",
250
+ "reason",
251
+ "expected_response",
252
+ "actual_response",
253
+ "created_at",
254
+ "flock_name",
255
+ "agent_name",
256
+ "flock_definition",
257
+ ]
258
+
259
+ return await _write_csv_file(
260
+ records=all_records,
261
+ path=csv_path,
262
+ headers=headers,
263
+ filename=csv_filename,
264
+ separator=separator,
265
+ )
266
+
267
+ async def _write_xlsx_file(
268
+ records: list[dict],
269
+ headers: list,
270
+ path: str | Path,
271
+ filename: str,
272
+ ) -> FileResponse:
273
+ """Writes an xlsx-file with the specified records."""
274
+ try:
275
+ import pandas as pd
276
+
277
+ # Convert records to a format suitable for
278
+ # pandas DataFrame
279
+ data_rows = []
280
+ for record in records:
281
+ row_data = {}
282
+ for header in headers:
283
+ value = getattr(record, header, None)
284
+ # Convert datetime to string for excel
285
+ if header == "created_at" and value:
286
+ row_data[header] = (
287
+ value.isoformat()
288
+ if isinstance(value, datetime)
289
+ else str(value)
290
+ )
291
+ else:
292
+ row_data[header] = str(value) if value is not None else ""
293
+ data_rows.append(row_data)
294
+
295
+ # Create DataFrame and write to Excel
296
+ df = pd.DataFrame(data_rows)
297
+ df.to_excel(str(path), index=False)
298
+
299
+ return FileResponse(
300
+ path=str(path),
301
+ filename=filename,
302
+ media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
303
+ headers={
304
+ "Content-Disposition": f"attachment; filename={filename}"
305
+ }
306
+ )
307
+
308
+ except Exception:
309
+ from fastapi import HTTPException
310
+
311
+ raise HTTPException(
312
+ status_code=500,
313
+ detail="Unable to create feedback Excel file"
314
+ )
315
+
316
+ async def _write_csv_file(
317
+ records: list[dict],
318
+ path: str | Path,
319
+ filename: str,
320
+ headers: list,
321
+ separator: str = ","
322
+ ) -> FileResponse:
323
+ """Writes a CSV_File with the specified records."""
324
+ try:
325
+ with open(path, "w", newline="", encoding="utf-8") as csvfile:
326
+ writer = csv.DictWriter(
327
+ csvfile,
328
+ fieldnames=headers,
329
+ delimiter=separator
330
+ )
331
+ writer.writeheader()
332
+ for record in records:
333
+ # Convert the Pydantic model
334
+ # to dict and ensure all fields are present
335
+ row_data = {}
336
+ for header in headers:
337
+ value = getattr(record, header, None)
338
+ # Convert datetime to ISO string for CSV
339
+ if header == "created_at" and value:
340
+ row_data[header] = (
341
+ value.isoformat()
342
+ if isinstance(value, datetime)
343
+ else str(value)
344
+ )
345
+ else:
346
+ row_data[header] = str(value) if value is not None else ""
347
+ writer.writerow(row_data)
348
+
349
+ return FileResponse(
350
+ path=str(path),
351
+ filename=filename,
352
+ media_type="text/csv",
353
+ headers={
354
+ "Content-Disposition": f"attachment; filename={filename}"
355
+ }
356
+ )
357
+ except Exception:
358
+ from fastapi import HTTPException
359
+
360
+ raise HTTPException(
361
+ status_code=500,
362
+ detail="Unable to create feedback-file"
363
+ )
@@ -455,6 +455,9 @@ class AzureTableSharedLinkStore(SharedLinkStoreInterface):
455
455
  "actual_response": record.actual_response,
456
456
  "created_at": record.created_at.isoformat(),
457
457
  }
458
+
459
+ # additional sanity check
460
+
458
461
  if record.flock_name is not None:
459
462
  entity["flock_name"] = record.flock_name
460
463
  if record.agent_name is not None:
@@ -520,38 +523,49 @@ class AzureTableSharedLinkStore(SharedLinkStoreInterface):
520
523
  tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
521
524
 
522
525
  # Use Azure Table Storage filtering to only get records for the specified agent
523
- filter_query = f"agent_name eq '{agent_name}'"
526
+ escaped_agent_name = agent_name.replace("'", "''")
527
+ filter_query = f"agent_name eq '{escaped_agent_name}'"
528
+
529
+ logger.debug(f"Querying feedback records with filter: {filter_query}")
524
530
 
525
531
  records = []
526
- async for entity in tbl_client.query_entities(filter_query):
527
- # Get flock_definition from blob if it exists
528
- flock_definition = None
529
- if "flock_blob_name" in entity:
530
- blob_name = entity["flock_blob_name"]
531
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
532
- try:
533
- blob_bytes = await (await blob_client.download_blob()).readall()
534
- flock_definition = blob_bytes.decode()
535
- except Exception as e:
536
- logger.error("Cannot download blob '%s' for feedback_id=%s: %s",
537
- blob_name, entity["RowKey"], e, exc_info=True)
538
- # Continue without flock_definition rather than failing
539
-
540
- records.append(FeedbackRecord(
541
- feedback_id=entity["RowKey"],
542
- share_id=entity.get("share_id"),
543
- context_type=entity["context_type"],
544
- reason=entity["reason"],
545
- expected_response=entity.get("expected_response"),
546
- actual_response=entity.get("actual_response"),
547
- flock_name=entity.get("flock_name"),
548
- agent_name=entity.get("agent_name"),
549
- flock_definition=flock_definition,
550
- created_at=entity["created_at"],
551
- ))
552
-
553
- logger.debug("Retrieved %d feedback records for agent %s", len(records), agent_name)
554
- return records
532
+ try:
533
+ async for entity in tbl_client.query_entities(filter_query):
534
+ # Get flock_definition from blob if it exists
535
+ flock_definition = None
536
+ if "flock_blob_name" in entity:
537
+ blob_name = entity["flock_blob_name"]
538
+ blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
539
+ try:
540
+ blob_bytes = await (await blob_client.download_blob()).readall()
541
+ flock_definition = blob_bytes.decode()
542
+ except Exception as e:
543
+ logger.error("Cannot download blob '%s' for feedback_id=%s: %s",
544
+ blob_name, entity["RowKey"], e, exc_info=True)
545
+ # Continue without flock_definition rather than failing
546
+
547
+ records.append(FeedbackRecord(
548
+ feedback_id=entity["RowKey"],
549
+ share_id=entity.get("share_id"),
550
+ context_type=entity["context_type"],
551
+ reason=entity["reason"],
552
+ expected_response=entity.get("expected_response"),
553
+ actual_response=entity.get("actual_response"),
554
+ flock_name=entity.get("flock_name"),
555
+ agent_name=entity.get("agent_name"),
556
+ flock_definition=flock_definition,
557
+ created_at=entity["created_at"],
558
+ ))
559
+
560
+ logger.debug("Retrieved %d feedback records for agent %s", len(records), agent_name)
561
+ return records
562
+
563
+ except Exception as e:
564
+ # Log the error.
565
+ logger.error(
566
+ f"Unable to query entries for agent {agent_name}. Exception: {e}"
567
+ )
568
+ return records
555
569
 
556
570
 
557
571
  # ----------------------- Factory Function -----------------------
@@ -1,22 +1,17 @@
1
1
  <script>
2
- // Create URL template for agent input forms
3
- window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agent_name="AGENT_PLACEHOLDER") }}';
2
+ // Create URL template for agent input forms
3
+ window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agent_name="AGENT_PLACEHOLDER") }}';
4
4
  </script>
5
5
 
6
6
  <article id="execution-form-content">
7
7
  <header>
8
8
  <h2>Run Flock</h2>
9
9
  </header>
10
- {% if flock and flock.agents %} <form hx-post="{{ url_for('htmx_run_flock') }}"
11
- hx-target="#results-display"
12
- hx-swap="innerHTML"
13
- hx-indicator="#run-loading-indicator"
14
- x-data="{ selectedAgentForInput: '' }">
15
-
10
+ {% if flock and flock.agents %} <form hx-post="{{ url_for('htmx_run_flock') }}" hx-target="#results-display"
11
+ hx-swap="innerHTML" hx-indicator="#run-loading-indicator" x-data="{ selectedAgentForInput: '' }">
12
+
16
13
  <label for="start_agent_name_select">Select Start Agent:</label>
17
- <select id="start_agent_name_select"
18
- name="start_agent_name"
19
- required x-model="selectedAgentForInput" @change="
14
+ <select id="start_agent_name_select" name="start_agent_name" required x-model="selectedAgentForInput" @change="
20
15
  if ($event.target.value) {
21
16
  const url = window.agentInputFormUrlTemplate.replace('AGENT_PLACEHOLDER', $event.target.value);
22
17
  htmx.ajax('GET', url, {target: '#dynamic-input-form-fields', swap: 'innerHTML', indicator: '#input-form-loading-indicator'});
@@ -26,7 +21,8 @@ window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agen
26
21
  ">
27
22
  <option value="" disabled {% if not selected_agent_name %}selected{% endif %}>-- Choose an agent --</option>
28
23
  {% for agent_name_key in flock.agents.keys() %}
29
- <option value="{{ agent_name_key }}" {% if selected_agent_name == agent_name_key %}selected{% endif %}>{{ agent_name_key }}</option>
24
+ <option value="{{ agent_name_key }}" {% if selected_agent_name==agent_name_key %}selected{% endif %}>{{
25
+ agent_name_key }}</option>
30
26
  {% endfor %}
31
27
  </select>
32
28
 
@@ -38,16 +34,12 @@ window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agen
38
34
  <progress indeterminate></progress> Loading input form...
39
35
  </div>
40
36
 
41
- <button type="submit" {% if not flock.agents %}disabled{% endif %}>Run Flock</button>
42
- <div id="share-agent-link-container" style="margin-top: 0.5rem;">
43
- <a href="#"
44
- id="shareAgentHtmxLink"
45
- hx-post="{{ url_for('htmx_generate_share_link') }}"
46
- hx-target="#shareLinkDisplayArea"
47
- hx-swap="innerHTML"
48
- hx-indicator="#share-loading-indicator"
49
- hx-include="#start_agent_name_select"
50
- style="text-decoration: underline; cursor: pointer; color: var(--pico-primary);">
37
+ <button type="submit" {% if not flock.agents %}disabled{% endif %}>Run Flock</button>
38
+ <div id="share-agent-link-container" style="margin-top: 0.5rem;">
39
+ <a href="#" id="shareAgentHtmxLink" hx-post="{{ url_for('htmx_generate_share_link') }}"
40
+ hx-target="#shareLinkDisplayArea" hx-swap="innerHTML" hx-indicator="#share-loading-indicator"
41
+ hx-include="#start_agent_name_select"
42
+ style="padding: 0.25rem 0.5rem; margin: 0.25rem; background: var(--pico-primary); color: white; text-decoration: none; border-radius: 4px; font-size: 0.875rem;">
51
43
  Create shareable link...
52
44
  </a>
53
45
  <span id="share-loading-indicator" class="htmx-indicator">
@@ -56,26 +48,46 @@ window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agen
56
48
  </div>
57
49
 
58
50
  <div id="feedback-download-container" style="margin-top: 0.5rem;">
59
- <a href="#"
60
- id="downloadFeedbackLink"
61
- x-show="selectedAgentForInput"
62
- hx-indicator="#download-feedback-loading-indicator"
63
- hx-include="#start_agent_name_select"
64
- :href="selectedAgentForInput ? '{{ url_for('chat_feedback_download', agent_name='AGENT_PLACEHOLDER') }}'.replace('AGENT_PLACEHOLDER', selectedAgentForInput) : '#'"
65
- style="text-decoration: underline; cursor: pointer; color: var(--pico-primary);"
66
- x-cloak>
67
- Download feedback for the selected agent...
68
- </a>
51
+ <div x-show="selectedAgentForInput" x-cloak
52
+ style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;">
53
+ <span style="padding: 0.25rem; text-decoration: underline;">Download feedback for selected
54
+ agent:</span>
55
+ <div>
56
+ <a href="#" id="downloadFeedbackLink" hx-indicator="#download-feedback-loading-indicator"
57
+ hx-include="#start_agent_name_select"
58
+ :href="selectedAgentForInput ? '{{ url_for('chat_feedback_download', agent_name='AGENT_PLACEHOLDER', format='csv') }}'.replace('AGENT_PLACEHOLDER', selectedAgentForInput) : '#'"
59
+ style="padding: 0.25rem 0.5rem; margin: 0.25rem; background: var(--pico-primary); color: white; text-decoration: none; border-radius: 4px; font-size: 0.875rem;">
60
+ CSV
61
+ </a>
62
+ <a href="#" id="downloadFeedbackLinkXlsx" hx-indicator="#download-feedback-loading-indicator"
63
+ hx-include="#start_agent_name_select"
64
+ :href="selectedAgentForInput ? '{{ url_for('chat_feedback_download', agent_name='AGENT_PLACEHOLDER', format='xlsx') }}'.replace('AGENT_PLACEHOLDER', selectedAgentForInput) : '#'"
65
+ style="padding: 0.25rem 0.5rem; margin: 0.25rem; background: var(--pico-primary); color: white; text-decoration: none; border-radius: 4px; font-size: 0.875rem;">
66
+ XLSX
67
+ </a>
68
+ </div>
69
+ </div>
69
70
  <span id="download-feedback-loading-indicator" class="htmx-indicator">
70
71
  <progress indeterminate></progress> Preparing file...
71
72
  </span>
72
73
  </div>
73
74
 
74
75
  <div id="feedback-download-container-all" style="margin-top: 0.5rem;">
75
- <a href="{{ url_for('chat_feedback_download_all')}}"
76
- id="downloadFeedbackLinkAll"
77
- style="text-decoration: underline; cursor: pointer; color: var(--pico-primary);"
78
- >Download feedback for all agents...</a>
76
+ <div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;">
77
+ <span style="padding: 0.25rem; text-decoration: underline;">Download feedback for all
78
+ agents:</span>
79
+ <div>
80
+ <a href="{{ url_for('chat_feedback_download_all', format='csv') }}" id="downloadFeedbackLinkAll"
81
+ style="padding: 0.25rem 0.5rem; margin: 0.25rem; background: var(--pico-primary); color: white; text-decoration: none; border-radius: 4px; font-size: 0.875rem;">
82
+ CSV
83
+ </a>
84
+ <a href="{{ url_for('chat_feedback_download_all', format='xlsx') }}"
85
+ id="downloadFeedbackLinkAllXlsx"
86
+ style="padding: 0.25rem 0.5rem; margin: 0.25rem; background: var(--pico-primary); color: white; text-decoration: none; border-radius: 4px; font-size: 0.875rem;">
87
+ XLSX
88
+ </a>
89
+ </div>
90
+ </div>
79
91
  <span id="download-feedback-loading-indicator-all" class="htmx-indicator">
80
92
  <progress indeterminate></progress> Preparing file...
81
93
  </span>
@@ -98,7 +110,7 @@ window.agentInputFormUrlTemplate = '{{ url_for("htmx_get_agent_input_form", agen
98
110
  </article>
99
111
 
100
112
  <script>
101
- document.addEventListener('DOMContentLoaded', function() {
113
+ document.addEventListener('DOMContentLoaded', function () {
102
114
  // All previous JavaScript for toggling share link visibility is removed.
103
115
  // If there are other unrelated JavaScript functions in this script block,
104
116
  // they would remain.
@@ -31,9 +31,18 @@
31
31
  </button>
32
32
  </li>
33
33
  <li>
34
- <a role="button" class="outline" href="{{ url_for('chat_feedback_download_all')}}" download>
35
- Feedback-Files
36
- </a>
34
+ <span style="padding: 0.25rem; text-decoration: underline;">Feedback-Files</span>
35
+ <div>
36
+ <a href="{{ url_for('chat_feedback_download_all', format='csv') }}" id="downloadFeedbackLinkAll"
37
+ style="padding: 0.25rem 0.5rem; margin: 0.25rem; background: var(--pico-primary); color: white; text-decoration: none; border-radius: 4px; font-size: 0.875rem;">
38
+ CSV
39
+ </a>
40
+ <a href="{{ url_for('chat_feedback_download_all', format='xlsx') }}"
41
+ id="downloadFeedbackLinkAllXlsx"
42
+ style="padding: 0.25rem 0.5rem; margin: 0.25rem; background: var(--pico-primary); color: white; text-decoration: none; border-radius: 4px; font-size: 0.875rem;">
43
+ XLSX
44
+ </a>
45
+ </div>
37
46
  </li>
38
47
  </ul>
39
48
  </nav>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flock-core
3
- Version: 0.4.527
3
+ Version: 0.4.529
4
4
  Summary: Declarative LLM Orchestration at Scale
5
5
  Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
6
6
  License-File: LICENSE
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
10
10
  Requires-Python: >=3.10
11
11
  Requires-Dist: aiosqlite>=0.21.0
12
12
  Requires-Dist: azure-data-tables>=12.7.0
13
+ Requires-Dist: azure-storage-blob>=12.25.1
13
14
  Requires-Dist: cloudpickle>=3.1.1
14
15
  Requires-Dist: croniter>=6.0.0
15
16
  Requires-Dist: devtools>=0.12.2
@@ -49,6 +50,8 @@ Requires-Dist: tqdm>=4.60.1
49
50
  Requires-Dist: uvicorn>=0.34.0
50
51
  Requires-Dist: wd-di>=0.2.14
51
52
  Requires-Dist: websockets>=15.0.1
53
+ Requires-Dist: werkzeug>=3.1.3
54
+ Requires-Dist: xlsxwriter>=3.2.3
52
55
  Provides-Extra: all
53
56
  Requires-Dist: azure-identity>=1.23.0; extra == 'all'
54
57
  Requires-Dist: azure-search-documents>=11.5.2; extra == 'all'
@@ -492,7 +492,7 @@ flock/tools/zendesk_tools.py,sha256=e7KMfHVl7wGbstwdz9CvoChyuoZfpS9n4TEtvrxawgI,
492
492
  flock/webapp/__init__.py,sha256=YtRbbyciN3Z2oMB9fdXZuvM3e49R8m2mY5qHLDoapRA,37
493
493
  flock/webapp/run.py,sha256=btKVwIqrFg3FhLRuj2RN_fazwaFat3Ue5yiFiIg60rQ,9054
494
494
  flock/webapp/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
495
- flock/webapp/app/chat.py,sha256=Wm7yr3j2Zjh2t6i5QF461m8FXZFfBMFtf7Sy_s2cigA,33159
495
+ flock/webapp/app/chat.py,sha256=cfEXOJRwobLZF6Ikj_x1Ev8ZVwhoukKF0xyklVKlCYk,30754
496
496
  flock/webapp/app/config.py,sha256=lqmneujnNZk-EFJV5cWpvxkqisxH3T3zT_YOI0JYThE,4809
497
497
  flock/webapp/app/dependencies.py,sha256=JUcwY1N6SZplU141lMN2wk9dOC9er5HCedrKTJN9wJk,5533
498
498
  flock/webapp/app/main.py,sha256=J3NiwW4fFTrp_YKa-HFXs3gLtLC6qu9LjIb_i-jJHMk,58426
@@ -502,13 +502,14 @@ flock/webapp/app/theme_mapper.py,sha256=QzWwLWpED78oYp3FjZ9zxv1KxCyj43m8MZ0fhfzz
502
502
  flock/webapp/app/utils.py,sha256=RF8DMKKAj1XPmm4txUdo2OdswI1ATQ7cqUm6G9JFDzA,2942
503
503
  flock/webapp/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
504
504
  flock/webapp/app/api/agent_management.py,sha256=5xqO94QjjAYvxImyjKV9EGUQOvo4n3eqs7pGwGPSQJ4,10394
505
- flock/webapp/app/api/execution.py,sha256=ovtjcBmm_S7Gyg1_hM4euBdIPDTZJZu-hCQa64tQS8o,21109
505
+ flock/webapp/app/api/execution.py,sha256=OzTjCP5CxAGdD2YrX7vI2qkQejqikX9Jn8_sq2o6yKA,18163
506
506
  flock/webapp/app/api/flock_management.py,sha256=1o-6-36kTnUjI3am_BqLpdrcz0aqFXrxE-hQHIFcCsg,4869
507
507
  flock/webapp/app/api/registry_viewer.py,sha256=IoInxJiRR0yFlecG_l2_eRc6l35RQQyEDMG9BcBkipY,1020
508
508
  flock/webapp/app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
509
+ flock/webapp/app/services/feedback_file_service.py,sha256=6WYJjml8lt_ULH5vq7JSWrPQPvUSLvp91qMBt-_tg5Q,9376
509
510
  flock/webapp/app/services/flock_service.py,sha256=olU1My3YYkrTCVIOYPgRted-8YgAop-Yi7G4gbRHTrg,14941
510
511
  flock/webapp/app/services/sharing_models.py,sha256=XeJk1akILV_1l-cIUaG8k_eYhjV3EWBCWZ2kpwbdImA,3609
511
- flock/webapp/app/services/sharing_store.py,sha256=vUIFSDppxPL65DP2OWczYe5aheuv_oHO2cynCtsRgtE,26649
512
+ flock/webapp/app/services/sharing_store.py,sha256=QjwG84PzEbzXyyHjFnDHz8Sf1pmB_4PY-_s9g8ht6S0,27163
512
513
  flock/webapp/app/templates/theme_mapper.html,sha256=z8ZY7nmk6PiUGzD_-px7wSXcEnuBM121rMq6u-2oaCo,14249
513
514
  flock/webapp/static/css/chat.css,sha256=Njc9gXfQzbXMrqtFJH2Yda-IQlwNPd2z4apXxzfA0sY,8169
514
515
  flock/webapp/static/css/components.css,sha256=WnicEHy3ptPzggKmyG9_oZp3X30EMJBUW3KEXaiUCUE,6018
@@ -537,7 +538,7 @@ flock/webapp/templates/partials/_dashboard_flock_properties_preview.html,sha256=
537
538
  flock/webapp/templates/partials/_dashboard_upload_flock_form.html,sha256=UWU_WpIsq6iw5FwK989ANmhzcCOcDKVjdakZ4kxY8lU,961
538
539
  flock/webapp/templates/partials/_dynamic_input_form_content.html,sha256=WYr2M7Mb5vKITFhIVXXEHppRx9IjGew91yLo1_I9kkI,1230
539
540
  flock/webapp/templates/partials/_env_vars_table.html,sha256=st8bRQpIJ3TJ_znEdyOwDT43ZhO2QTLne2IZNxQdQNM,1106
540
- flock/webapp/templates/partials/_execution_form.html,sha256=ln73ShbXU1ZNYoBkryvuK4RnPebimNyHHE9VK4heX0s,5113
541
+ flock/webapp/templates/partials/_execution_form.html,sha256=lYdX3qqlKZ4_6EQttv_zEC9PR75qayoJ0p1IfebC2Lw,6733
541
542
  flock/webapp/templates/partials/_execution_view_container.html,sha256=w4QEr-yPs0k1vF2oxMhhPoOr-ki0YJzzUltGmZgfRfY,1672
542
543
  flock/webapp/templates/partials/_flock_file_list.html,sha256=3F1RE1EaeRSQeyIWeA2ALHU2j4oGwureDtY6k1aZVjQ,1261
543
544
  flock/webapp/templates/partials/_flock_properties_form.html,sha256=kUHoxB6_C_R9nMDD_ClLRDL_9zQ6E8gFCrlkEkqcUYo,2904
@@ -545,7 +546,7 @@ flock/webapp/templates/partials/_flock_upload_form.html,sha256=IK4Kk_82z3m9zlutK
545
546
  flock/webapp/templates/partials/_header_flock_status.html,sha256=reNB4Prsu9lObz5tFhGk3AMe4bNw92gDJZUKABVaVBM,158
546
547
  flock/webapp/templates/partials/_load_manager_view.html,sha256=lu_2yKJcBPF8yZEeA0rnCPpYODieVfQgjq8HzKoPTzs,2950
547
548
  flock/webapp/templates/partials/_registry_table.html,sha256=z4EW5G3DTknymBeSlpL2PZLcb2143P35upMnmHFfeJs,715
548
- flock/webapp/templates/partials/_registry_viewer_content.html,sha256=GNI5LJsxefjwhxP24Q2flRgs-2yWAG-f-KFAAuV23tk,2347
549
+ flock/webapp/templates/partials/_registry_viewer_content.html,sha256=rF3jFxP6mNkuf-nRRG_a0S8wWL4gdgoMRgxfpQGy0P4,3067
549
550
  flock/webapp/templates/partials/_results_display.html,sha256=1UJvCeyJTPxuJYUcGUM8O8uhNvCxxOfxpCjC-PCr80U,5268
550
551
  flock/webapp/templates/partials/_settings_env_content.html,sha256=16h1ppTGNY7wNkxQkaNhouTNN22tlw_u5rBk6cdhzFk,597
551
552
  flock/webapp/templates/partials/_settings_theme_content.html,sha256=TPkqLyXJHwWOOgjLVURLpHA2JJRncZGf78Q6olIZIJc,852
@@ -562,8 +563,8 @@ flock/workflow/agent_execution_activity.py,sha256=Gy6FtuVAjf0NiUXmC3syS2eJpNQF4R
562
563
  flock/workflow/flock_workflow.py,sha256=iSUF_soFvWar0ffpkzE4irkDZRx0p4HnwmEBi_Ne2sY,9666
563
564
  flock/workflow/temporal_config.py,sha256=3_8O7SDEjMsSMXsWJBfnb6XTp0TFaz39uyzSlMTSF_I,3988
564
565
  flock/workflow/temporal_setup.py,sha256=YIHnSBntzOchHfMSh8hoLeNXrz3B1UbR14YrR6soM7A,1606
565
- flock_core-0.4.527.dist-info/METADATA,sha256=RvdNldnNPaR7aVN0c_pULSlP166YPJr5uJwey78shgo,22786
566
- flock_core-0.4.527.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
567
- flock_core-0.4.527.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
568
- flock_core-0.4.527.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
569
- flock_core-0.4.527.dist-info/RECORD,,
566
+ flock_core-0.4.529.dist-info/METADATA,sha256=WzwwdzvEuCbx-eWDDlfIjYRv8QLXgyznLqy0APSTKvc,22893
567
+ flock_core-0.4.529.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
568
+ flock_core-0.4.529.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
569
+ flock_core-0.4.529.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
570
+ flock_core-0.4.529.dist-info/RECORD,,