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.
- flock/webapp/app/api/execution.py +57 -146
- flock/webapp/app/chat.py +44 -123
- flock/webapp/app/services/feedback_file_service.py +363 -0
- flock/webapp/app/services/sharing_store.py +44 -30
- flock/webapp/templates/partials/_execution_form.html +49 -37
- flock/webapp/templates/partials/_registry_viewer_content.html +12 -3
- {flock_core-0.4.527.dist-info → flock_core-0.4.529.dist-info}/METADATA +4 -1
- {flock_core-0.4.527.dist-info → flock_core-0.4.529.dist-info}/RECORD +11 -10
- {flock_core-0.4.527.dist-info → flock_core-0.4.529.dist-info}/WHEEL +0 -0
- {flock_core-0.4.527.dist-info → flock_core-0.4.529.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.527.dist-info → flock_core-0.4.529.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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 %}
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
566
|
-
flock_core-0.4.
|
|
567
|
-
flock_core-0.4.
|
|
568
|
-
flock_core-0.4.
|
|
569
|
-
flock_core-0.4.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|