qalita 2.3.0__py3-none-any.whl → 2.3.2__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.
- qalita/commands/agent.py +89 -1
- qalita/commands/pack.py +116 -143
- qalita/internal/utils.py +1 -1
- qalita/web/blueprints/studio.py +40 -1
- qalita/web/templates/studio/agent-panel.html +71 -12
- {qalita-2.3.0.dist-info → qalita-2.3.2.dist-info}/METADATA +1 -1
- {qalita-2.3.0.dist-info → qalita-2.3.2.dist-info}/RECORD +10 -10
- {qalita-2.3.0.dist-info → qalita-2.3.2.dist-info}/WHEEL +1 -1
- {qalita-2.3.0.dist-info → qalita-2.3.2.dist-info}/entry_points.txt +0 -0
- {qalita-2.3.0.dist-info → qalita-2.3.2.dist-info}/licenses/LICENSE +0 -0
qalita/commands/agent.py
CHANGED
|
@@ -465,6 +465,9 @@ def run(
|
|
|
465
465
|
# check routines before checking jobs
|
|
466
466
|
check_routines(config, agent_start_datetime)
|
|
467
467
|
|
|
468
|
+
# try to claim one pending unassigned job compatible with this agent
|
|
469
|
+
claim_unassigned_jobs(config)
|
|
470
|
+
|
|
468
471
|
check_job = send_api_request(
|
|
469
472
|
request=f'/api/v1/agents/{agent_conf["context"]["remote"]["id"]}/jobs/next',
|
|
470
473
|
mode="get",
|
|
@@ -1066,7 +1069,6 @@ def create_scheduled_job(routine, agent_conf):
|
|
|
1066
1069
|
request=f"/api/v2/jobs/create",
|
|
1067
1070
|
mode="post",
|
|
1068
1071
|
data={
|
|
1069
|
-
"agent_id": agent_conf["context"]["remote"]["id"],
|
|
1070
1072
|
"source_id": routine["source"]["id"],
|
|
1071
1073
|
"target_id": routine["target"]["id"] if routine.get("target") else None,
|
|
1072
1074
|
"pack_id": routine["pack"]["id"],
|
|
@@ -1238,3 +1240,89 @@ def check_routines(config, agent_start_datetime):
|
|
|
1238
1240
|
ROUTINE_LAST_SCHEDULED_UTC[routine.get("id")] = datetime.now(timezone.utc)
|
|
1239
1241
|
else:
|
|
1240
1242
|
logger.warning("Can't fetch routines or jobs from the platform")
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
def claim_unassigned_jobs(config):
|
|
1246
|
+
"""Claim one pending unassigned job that this agent can execute.
|
|
1247
|
+
|
|
1248
|
+
Strategy:
|
|
1249
|
+
- Fetch all jobs (v2)
|
|
1250
|
+
- Select first job with status == pending and no agent assigned
|
|
1251
|
+
- Check local capability: source (and target if provided) must exist locally
|
|
1252
|
+
- PUT to assign agent_id to this agent; on success, run job
|
|
1253
|
+
- On contention (another agent already claimed), ignore and return
|
|
1254
|
+
"""
|
|
1255
|
+
try:
|
|
1256
|
+
source_conf = config.load_source_config(verbose=False)
|
|
1257
|
+
agent_conf = config.load_agent_config()
|
|
1258
|
+
local_source_ids = [s["id"] for s in source_conf["sources"] if "id" in s]
|
|
1259
|
+
|
|
1260
|
+
r = send_api_request(
|
|
1261
|
+
request=f"/api/v2/jobs",
|
|
1262
|
+
mode="get",
|
|
1263
|
+
)
|
|
1264
|
+
if r.status_code not in [200, 204]:
|
|
1265
|
+
return
|
|
1266
|
+
jobs = r.json()
|
|
1267
|
+
if not isinstance(jobs, list):
|
|
1268
|
+
return
|
|
1269
|
+
|
|
1270
|
+
def extract_id(maybe_obj, fallback_key):
|
|
1271
|
+
if isinstance(maybe_obj, dict):
|
|
1272
|
+
return maybe_obj.get("id")
|
|
1273
|
+
return maybe_obj if isinstance(maybe_obj, int) else None
|
|
1274
|
+
|
|
1275
|
+
for job in jobs:
|
|
1276
|
+
try:
|
|
1277
|
+
if job.get("status") != "pending":
|
|
1278
|
+
continue
|
|
1279
|
+
# unassigned: support either agent_id field or nested agent object
|
|
1280
|
+
is_unassigned = (
|
|
1281
|
+
(job.get("agent_id") is None or job.get("agent_id") == 0)
|
|
1282
|
+
and not job.get("agent")
|
|
1283
|
+
)
|
|
1284
|
+
if not is_unassigned:
|
|
1285
|
+
continue
|
|
1286
|
+
|
|
1287
|
+
src_id = extract_id(job.get("source"), "source_id") or job.get("source_id")
|
|
1288
|
+
if src_id not in local_source_ids:
|
|
1289
|
+
continue
|
|
1290
|
+
tgt_id = extract_id(job.get("target"), "target_id") or job.get("target_id")
|
|
1291
|
+
if tgt_id is not None and tgt_id not in local_source_ids:
|
|
1292
|
+
continue
|
|
1293
|
+
|
|
1294
|
+
# attempt to claim
|
|
1295
|
+
claim_resp = send_api_request(
|
|
1296
|
+
request=f"/api/v2/jobs/{job['id']}",
|
|
1297
|
+
mode="put",
|
|
1298
|
+
data={
|
|
1299
|
+
"agent_id": agent_conf["context"]["remote"]["id"],
|
|
1300
|
+
},
|
|
1301
|
+
)
|
|
1302
|
+
if claim_resp.status_code != 200:
|
|
1303
|
+
# likely contention: someone else claimed
|
|
1304
|
+
continue
|
|
1305
|
+
|
|
1306
|
+
# extract version ids if present
|
|
1307
|
+
src_ver_id = extract_id(job.get("source_version"), "source_version_id") or job.get("source_version_id")
|
|
1308
|
+
tgt_ver_id = extract_id(job.get("target_version"), "target_version_id") or job.get("target_version_id")
|
|
1309
|
+
pack_id = extract_id(job.get("pack"), "pack_id") or job.get("pack_id")
|
|
1310
|
+
pack_ver_id = extract_id(job.get("pack_version"), "pack_version_id") or job.get("pack_version_id")
|
|
1311
|
+
|
|
1312
|
+
logger.info(f"Claimed unassigned job {job['id']} for source {src_id} pack {pack_id}")
|
|
1313
|
+
job_run(
|
|
1314
|
+
src_id,
|
|
1315
|
+
src_ver_id,
|
|
1316
|
+
tgt_id,
|
|
1317
|
+
tgt_ver_id,
|
|
1318
|
+
pack_id,
|
|
1319
|
+
pack_ver_id,
|
|
1320
|
+
job=job,
|
|
1321
|
+
)
|
|
1322
|
+
# claim and run only one per loop
|
|
1323
|
+
return
|
|
1324
|
+
except Exception as _:
|
|
1325
|
+
# Be resilient; don't break the worker loop on any unexpected job shape
|
|
1326
|
+
continue
|
|
1327
|
+
except Exception as _:
|
|
1328
|
+
return
|
qalita/commands/pack.py
CHANGED
|
@@ -586,20 +586,37 @@ def publish_new_pack_version(api_url, pack_id, pack_version, registry_id, pack_n
|
|
|
586
586
|
source_dir=source_dir,
|
|
587
587
|
config=config,
|
|
588
588
|
)
|
|
589
|
-
# Best-effort: also update
|
|
589
|
+
# Best-effort: also update metadata (avatar, description, url, visibility, config, readme)
|
|
590
|
+
# and compatibility if provided in properties.yaml so the new version push also refreshes pack details.
|
|
590
591
|
try:
|
|
591
592
|
pack_dir = source_dir or f"./{pack_name}_pack"
|
|
592
593
|
props = load_pack_properties(pack_dir) or {}
|
|
594
|
+
pack_icon = load_base64_encoded_image(os.path.join(pack_dir, "icon.png"))
|
|
595
|
+
readme = load_base64_encoded_text(os.path.join(pack_dir, "README.md"))
|
|
596
|
+
pack_config = load_json_config(os.path.join(pack_dir, "pack_conf.json"))
|
|
597
|
+
pack_type = props.get("type", "")
|
|
598
|
+
pack_description = props.get("description", "")
|
|
599
|
+
pack_url = props.get("url", "")
|
|
600
|
+
pack_visibility = props.get("visibility", "private")
|
|
593
601
|
compat = props.get("compatible_sources")
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
+
|
|
603
|
+
# Update full metadata
|
|
604
|
+
update_pack_metadata(
|
|
605
|
+
api_url,
|
|
606
|
+
pack_id,
|
|
607
|
+
pack_icon,
|
|
608
|
+
pack_config,
|
|
609
|
+
pack_type,
|
|
610
|
+
pack_description,
|
|
611
|
+
pack_url,
|
|
612
|
+
pack_version,
|
|
613
|
+
pack_visibility,
|
|
614
|
+
readme,
|
|
615
|
+
compatible_sources=compat,
|
|
616
|
+
config=config,
|
|
617
|
+
)
|
|
602
618
|
except Exception:
|
|
619
|
+
# Non-fatal if metadata refresh fails; continue to publish version
|
|
603
620
|
pass
|
|
604
621
|
publish_response = _request_with_optional_config(
|
|
605
622
|
config,
|
|
@@ -689,6 +706,84 @@ def handle_version_matching(existing_versions, new_version):
|
|
|
689
706
|
return None
|
|
690
707
|
|
|
691
708
|
|
|
709
|
+
def _push_with_context(agent_conf, pack_properties, pack_directory, config):
|
|
710
|
+
"""Shared push routine used by CLI and Web UI.
|
|
711
|
+
|
|
712
|
+
Expects absolute or relative `pack_directory` and a loaded `pack_properties` dict.
|
|
713
|
+
"""
|
|
714
|
+
# Extract pack details
|
|
715
|
+
pack_name = pack_properties.get("name")
|
|
716
|
+
pack_version = pack_properties.get("version")
|
|
717
|
+
pack_description = pack_properties.get("description", "")
|
|
718
|
+
pack_url = pack_properties.get("url", "")
|
|
719
|
+
pack_type = pack_properties.get("type", "")
|
|
720
|
+
pack_visibility = pack_properties.get("visibility", "private")
|
|
721
|
+
|
|
722
|
+
# Handle pack icon and README/config
|
|
723
|
+
pack_icon = load_base64_encoded_image(os.path.join(pack_directory, "icon.png"))
|
|
724
|
+
readme = load_base64_encoded_text(os.path.join(pack_directory, "README.md"))
|
|
725
|
+
pack_config = load_json_config(os.path.join(pack_directory, "pack_conf.json"))
|
|
726
|
+
|
|
727
|
+
# Check existing packs and versions
|
|
728
|
+
pack_id, existing_versions = find_existing_pack(
|
|
729
|
+
agent_conf["context"]["local"]["url"], pack_name, config=config
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
if pack_id:
|
|
733
|
+
new_version_entry = handle_version_matching(existing_versions, pack_version)
|
|
734
|
+
if not new_version_entry:
|
|
735
|
+
# Try to load compatible_sources from properties.yaml (optional)
|
|
736
|
+
compat = (
|
|
737
|
+
pack_properties.get("compatible_sources")
|
|
738
|
+
if isinstance(pack_properties, dict)
|
|
739
|
+
else None
|
|
740
|
+
)
|
|
741
|
+
update_pack_metadata(
|
|
742
|
+
agent_conf["context"]["local"]["url"],
|
|
743
|
+
pack_id,
|
|
744
|
+
pack_icon,
|
|
745
|
+
pack_config,
|
|
746
|
+
pack_type,
|
|
747
|
+
pack_description,
|
|
748
|
+
pack_url,
|
|
749
|
+
pack_version,
|
|
750
|
+
pack_visibility,
|
|
751
|
+
readme,
|
|
752
|
+
compatible_sources=compat,
|
|
753
|
+
config=config,
|
|
754
|
+
)
|
|
755
|
+
logger.success(
|
|
756
|
+
"Pack metadata updated successfully ! \nIf you wanted to upload a new pack version, you must change the pack [version] attribute in properties.yaml"
|
|
757
|
+
)
|
|
758
|
+
else:
|
|
759
|
+
publish_new_pack_version(
|
|
760
|
+
agent_conf["context"]["local"]["url"],
|
|
761
|
+
pack_id,
|
|
762
|
+
pack_version,
|
|
763
|
+
agent_conf["registries"][0]["id"],
|
|
764
|
+
pack_name,
|
|
765
|
+
source_dir=pack_directory,
|
|
766
|
+
config=config,
|
|
767
|
+
)
|
|
768
|
+
else:
|
|
769
|
+
create_new_pack(
|
|
770
|
+
agent_conf["context"]["local"]["url"],
|
|
771
|
+
agent_conf["registries"][0]["id"],
|
|
772
|
+
pack_name,
|
|
773
|
+
pack_icon,
|
|
774
|
+
pack_config,
|
|
775
|
+
pack_type,
|
|
776
|
+
pack_description,
|
|
777
|
+
pack_url,
|
|
778
|
+
pack_version,
|
|
779
|
+
pack_visibility,
|
|
780
|
+
readme,
|
|
781
|
+
source_dir=pack_directory,
|
|
782
|
+
config=config,
|
|
783
|
+
)
|
|
784
|
+
logger.success("New pack created successfully!")
|
|
785
|
+
|
|
786
|
+
|
|
692
787
|
@pack.command()
|
|
693
788
|
@click.option("-n", "--name", help="Name of the package", envvar="QALITA_PACK_NAME")
|
|
694
789
|
@pass_config
|
|
@@ -725,78 +820,14 @@ def push(config, name):
|
|
|
725
820
|
logger.error("Failed to load agent configuration.")
|
|
726
821
|
return
|
|
727
822
|
|
|
728
|
-
#
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
pack_visibility = pack_properties.get("visibility", "private")
|
|
735
|
-
|
|
736
|
-
# Handle pack icon
|
|
737
|
-
pack_icon = load_base64_encoded_image(f"{pack_directory}/icon.png")
|
|
738
|
-
|
|
739
|
-
# Handle README file
|
|
740
|
-
readme = load_base64_encoded_text(f"{pack_directory}/README.md")
|
|
741
|
-
|
|
742
|
-
# Load pack config
|
|
743
|
-
pack_config = load_json_config(f"{pack_directory}/pack_conf.json")
|
|
744
|
-
|
|
745
|
-
# Check existing packs and versions
|
|
746
|
-
pack_id, existing_versions = find_existing_pack(
|
|
747
|
-
agent_conf["context"]["local"]["url"], pack_name, config=config
|
|
823
|
+
# Shared push routine
|
|
824
|
+
_push_with_context(
|
|
825
|
+
agent_conf,
|
|
826
|
+
pack_properties,
|
|
827
|
+
os.path.abspath(pack_directory),
|
|
828
|
+
config,
|
|
748
829
|
)
|
|
749
830
|
|
|
750
|
-
if pack_id:
|
|
751
|
-
# Handle version matching and decide whether to add a new asset or update metadata
|
|
752
|
-
new_version_entry = handle_version_matching(existing_versions, pack_version)
|
|
753
|
-
|
|
754
|
-
if not new_version_entry:
|
|
755
|
-
# Try to load compatible_sources from properties.yaml (optional)
|
|
756
|
-
compat = pack_properties.get("compatible_sources") if isinstance(pack_properties, dict) else None
|
|
757
|
-
update_pack_metadata(
|
|
758
|
-
agent_conf["context"]["local"]["url"],
|
|
759
|
-
pack_id,
|
|
760
|
-
pack_icon,
|
|
761
|
-
pack_config,
|
|
762
|
-
pack_type,
|
|
763
|
-
pack_description,
|
|
764
|
-
pack_url,
|
|
765
|
-
pack_version,
|
|
766
|
-
pack_visibility,
|
|
767
|
-
readme,
|
|
768
|
-
compatible_sources=compat,
|
|
769
|
-
config=config,
|
|
770
|
-
)
|
|
771
|
-
logger.success("Pack metadata updated successfully ! \nIf you wanted to upload a new pack version, you must change the pack [version] attribute in properties.yaml")
|
|
772
|
-
else:
|
|
773
|
-
publish_new_pack_version(
|
|
774
|
-
agent_conf["context"]["local"]["url"],
|
|
775
|
-
pack_id,
|
|
776
|
-
pack_version,
|
|
777
|
-
agent_conf["registries"][0]["id"],
|
|
778
|
-
pack_name,
|
|
779
|
-
source_dir=os.path.abspath(pack_directory),
|
|
780
|
-
config=config,
|
|
781
|
-
)
|
|
782
|
-
else:
|
|
783
|
-
create_new_pack(
|
|
784
|
-
agent_conf["context"]["local"]["url"],
|
|
785
|
-
agent_conf["registries"][0]["id"],
|
|
786
|
-
pack_name,
|
|
787
|
-
pack_icon,
|
|
788
|
-
pack_config,
|
|
789
|
-
pack_type,
|
|
790
|
-
pack_description,
|
|
791
|
-
pack_url,
|
|
792
|
-
pack_version,
|
|
793
|
-
pack_visibility,
|
|
794
|
-
readme,
|
|
795
|
-
source_dir=os.path.abspath(pack_directory),
|
|
796
|
-
config=config,
|
|
797
|
-
)
|
|
798
|
-
logger.success("New pack created successfully!")
|
|
799
|
-
|
|
800
831
|
logger.info("Pack pushed successfully.")
|
|
801
832
|
|
|
802
833
|
except Exception as e:
|
|
@@ -842,72 +873,14 @@ def push_from_directory(config, pack_directory):
|
|
|
842
873
|
logger.error(msg)
|
|
843
874
|
return False, msg
|
|
844
875
|
|
|
845
|
-
#
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
pack_visibility = pack_properties.get("visibility", "private")
|
|
852
|
-
|
|
853
|
-
# Handle pack icon and README
|
|
854
|
-
pack_icon = load_base64_encoded_image(os.path.join(pack_directory, "icon.png"))
|
|
855
|
-
readme = load_base64_encoded_text(os.path.join(pack_directory, "README.md"))
|
|
856
|
-
pack_config = load_json_config(os.path.join(pack_directory, "pack_conf.json"))
|
|
857
|
-
|
|
858
|
-
# Check existing packs and versions
|
|
859
|
-
pack_id, existing_versions = find_existing_pack(
|
|
860
|
-
agent_conf["context"]["local"]["url"], pack_name, config=config
|
|
876
|
+
# Shared push routine
|
|
877
|
+
_push_with_context(
|
|
878
|
+
agent_conf,
|
|
879
|
+
pack_properties,
|
|
880
|
+
pack_directory,
|
|
881
|
+
config,
|
|
861
882
|
)
|
|
862
883
|
|
|
863
|
-
if pack_id:
|
|
864
|
-
new_version_entry = handle_version_matching(existing_versions, pack_version)
|
|
865
|
-
if not new_version_entry:
|
|
866
|
-
# Try to load compatible_sources from properties.yaml (optional)
|
|
867
|
-
compat = pack_properties.get("compatible_sources") if isinstance(pack_properties, dict) else None
|
|
868
|
-
update_pack_metadata(
|
|
869
|
-
agent_conf["context"]["local"]["url"],
|
|
870
|
-
pack_id,
|
|
871
|
-
pack_icon,
|
|
872
|
-
pack_config,
|
|
873
|
-
pack_type,
|
|
874
|
-
pack_description,
|
|
875
|
-
pack_url,
|
|
876
|
-
pack_version,
|
|
877
|
-
pack_visibility,
|
|
878
|
-
readme,
|
|
879
|
-
compatible_sources=compat,
|
|
880
|
-
config=config,
|
|
881
|
-
)
|
|
882
|
-
logger.success("Pack metadata updated successfully ! \nIf you wanted to upload a new pack version, you must change the pack [version] attribute in properties.yaml")
|
|
883
|
-
else:
|
|
884
|
-
publish_new_pack_version(
|
|
885
|
-
agent_conf["context"]["local"]["url"],
|
|
886
|
-
pack_id,
|
|
887
|
-
pack_version,
|
|
888
|
-
agent_conf["registries"][0]["id"],
|
|
889
|
-
pack_name,
|
|
890
|
-
source_dir=pack_directory,
|
|
891
|
-
config=config,
|
|
892
|
-
)
|
|
893
|
-
else:
|
|
894
|
-
create_new_pack(
|
|
895
|
-
agent_conf["context"]["local"]["url"],
|
|
896
|
-
agent_conf["registries"][0]["id"],
|
|
897
|
-
pack_name,
|
|
898
|
-
pack_icon,
|
|
899
|
-
pack_config,
|
|
900
|
-
pack_type,
|
|
901
|
-
pack_description,
|
|
902
|
-
pack_url,
|
|
903
|
-
pack_version,
|
|
904
|
-
pack_visibility,
|
|
905
|
-
readme,
|
|
906
|
-
source_dir=pack_directory,
|
|
907
|
-
config=config,
|
|
908
|
-
)
|
|
909
|
-
logger.success("New pack created successfully!")
|
|
910
|
-
|
|
911
884
|
logger.info("Pack pushed successfully.")
|
|
912
885
|
return True, "Pack pushed successfully."
|
|
913
886
|
except Exception as e:
|
qalita/internal/utils.py
CHANGED
qalita/web/blueprints/studio.py
CHANGED
|
@@ -111,6 +111,17 @@ def _augment_prompt_with_context(prompt: str, issue_id: str | None, source_id: s
|
|
|
111
111
|
return meta + base
|
|
112
112
|
|
|
113
113
|
|
|
114
|
+
def _cloud_enabled() -> bool:
|
|
115
|
+
"""Return whether Studio cloud providers are enabled via env flag.
|
|
116
|
+
|
|
117
|
+
Env: QALITA_STUDIO_ENABLE_CLOUD = 1|true|yes|on to enable. Default: disabled.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
raw = str(os.getenv("QALITA_STUDIO_ENABLE_CLOUD", "0") or "").strip().lower()
|
|
121
|
+
return raw in ("1", "true", "yes", "on")
|
|
122
|
+
except Exception:
|
|
123
|
+
return False
|
|
124
|
+
|
|
114
125
|
def _studio_conv_dir() -> str:
|
|
115
126
|
"""Return the conversations directory, ensuring it exists."""
|
|
116
127
|
root = _qalita_home()
|
|
@@ -261,8 +272,16 @@ def studio_status():
|
|
|
261
272
|
current_provider = next(iter(data["providers"].keys()))
|
|
262
273
|
except Exception:
|
|
263
274
|
current_provider = None
|
|
275
|
+
# Enforce local-only when cloud is disabled
|
|
276
|
+
if not _cloud_enabled() and current_provider and current_provider != "local":
|
|
277
|
+
current_provider = "local"
|
|
264
278
|
return jsonify(
|
|
265
|
-
{
|
|
279
|
+
{
|
|
280
|
+
"configured": exists,
|
|
281
|
+
"config": data,
|
|
282
|
+
"current_provider": current_provider,
|
|
283
|
+
"cloud_enabled": _cloud_enabled(),
|
|
284
|
+
}
|
|
266
285
|
)
|
|
267
286
|
|
|
268
287
|
|
|
@@ -294,6 +313,15 @@ def studio_save_config():
|
|
|
294
313
|
)
|
|
295
314
|
set_current = bool(payload.get("set_current"))
|
|
296
315
|
if provider and conf is not None:
|
|
316
|
+
# Block saving non-local provider when cloud is disabled
|
|
317
|
+
if provider != "local" and not _cloud_enabled():
|
|
318
|
+
return (
|
|
319
|
+
jsonify({
|
|
320
|
+
"ok": False,
|
|
321
|
+
"message": "Cloud providers are disabled. Set QALITA_STUDIO_ENABLE_CLOUD=1 to enable.",
|
|
322
|
+
}),
|
|
323
|
+
403,
|
|
324
|
+
)
|
|
297
325
|
providers[provider] = conf
|
|
298
326
|
if set_current:
|
|
299
327
|
current["current_provider"] = provider
|
|
@@ -367,11 +395,14 @@ def list_providers():
|
|
|
367
395
|
{"id": "claude", "name": "Claude", "logo": "/static/sources-logos/api.svg"},
|
|
368
396
|
{"id": "gemini", "name": "Gemini", "logo": "/static/sources-logos/api.svg"},
|
|
369
397
|
]
|
|
398
|
+
if not _cloud_enabled():
|
|
399
|
+
available = [it for it in available if it.get("id") == "local"]
|
|
370
400
|
return jsonify(
|
|
371
401
|
{
|
|
372
402
|
"available": available,
|
|
373
403
|
"current": current,
|
|
374
404
|
"configs": providers,
|
|
405
|
+
"cloud_enabled": _cloud_enabled(),
|
|
375
406
|
}
|
|
376
407
|
)
|
|
377
408
|
|
|
@@ -383,6 +414,14 @@ def check_remote():
|
|
|
383
414
|
Body: { "provider": "openai"|"mistral", "api_key": "...", "model": "..." }
|
|
384
415
|
Returns: { ok: bool, message?: str, provider: str }
|
|
385
416
|
"""
|
|
417
|
+
if not _cloud_enabled():
|
|
418
|
+
return (
|
|
419
|
+
jsonify({
|
|
420
|
+
"ok": False,
|
|
421
|
+
"message": "Cloud providers are disabled. Set QALITA_STUDIO_ENABLE_CLOUD=1 to enable.",
|
|
422
|
+
}),
|
|
423
|
+
403,
|
|
424
|
+
)
|
|
386
425
|
data = request.get_json(silent=True) or {}
|
|
387
426
|
provider = (data.get("provider") or "").strip().lower()
|
|
388
427
|
api_key = (data.get("api_key") or "").strip()
|
|
@@ -311,6 +311,8 @@
|
|
|
311
311
|
const r = await fetch('/studio/status');
|
|
312
312
|
const j = await r.json();
|
|
313
313
|
lastStatus = j;
|
|
314
|
+
// Rebuild provider options depending on cloud flag
|
|
315
|
+
setProviderOptions(!!j.cloud_enabled);
|
|
314
316
|
const current = j.current_provider || 'local';
|
|
315
317
|
if (providerSelect) providerSelect.value = current;
|
|
316
318
|
updateProviderUI(current, j);
|
|
@@ -410,6 +412,38 @@
|
|
|
410
412
|
} catch (e) { if (msg) msg.textContent = 'Connectivity failed'; }
|
|
411
413
|
}
|
|
412
414
|
|
|
415
|
+
function setProviderOptions(cloudEnabled) {
|
|
416
|
+
try {
|
|
417
|
+
if (!providerSelect) return;
|
|
418
|
+
// Desired list depending on cloud flag
|
|
419
|
+
var desired = cloudEnabled ? [
|
|
420
|
+
{ v: 'local', t: 'Ollama' },
|
|
421
|
+
{ v: 'openai', t: 'ChatGPT' },
|
|
422
|
+
{ v: 'mistral', t: 'Mistral' },
|
|
423
|
+
{ v: 'claude', t: 'Claude' },
|
|
424
|
+
{ v: 'gemini', t: 'Gemini' }
|
|
425
|
+
] : [
|
|
426
|
+
{ v: 'local', t: 'Ollama' }
|
|
427
|
+
];
|
|
428
|
+
// If options already match, skip rebuild (lightweight check by values set)
|
|
429
|
+
var currentValues = Array.prototype.map.call(providerSelect.options || [], function (o) { return o && o.value; });
|
|
430
|
+
var desiredValues = desired.map(function (d) { return d.v; });
|
|
431
|
+
var equal = currentValues.length === desiredValues.length && currentValues.every(function (v, i) { return v === desiredValues[i]; });
|
|
432
|
+
if (!equal) {
|
|
433
|
+
providerSelect.innerHTML = '';
|
|
434
|
+
desired.forEach(function (d) {
|
|
435
|
+
var opt = document.createElement('option');
|
|
436
|
+
opt.value = d.v; opt.textContent = d.t;
|
|
437
|
+
providerSelect.appendChild(opt);
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// Force selection to local if cloud is disabled
|
|
441
|
+
if (!cloudEnabled) {
|
|
442
|
+
providerSelect.value = 'local';
|
|
443
|
+
}
|
|
444
|
+
} catch (e) { /* noop */ }
|
|
445
|
+
}
|
|
446
|
+
|
|
413
447
|
function updateProviderUI(provider, statusObj) {
|
|
414
448
|
// Update logo
|
|
415
449
|
let src = '/static/ollama.png';
|
|
@@ -421,15 +455,29 @@
|
|
|
421
455
|
if (providerLogoInline) { providerLogoInline.src = src; providerLogoInline.alt = alt; }
|
|
422
456
|
if (providerLogoRemote) { providerLogoRemote.src = src; providerLogoRemote.alt = alt; }
|
|
423
457
|
// Toggle setup cards
|
|
458
|
+
var cloudEnabled = !!(statusObj && statusObj.cloud_enabled);
|
|
459
|
+
if (provider !== 'local' && !cloudEnabled) {
|
|
460
|
+
// Force local UI when cloud disabled
|
|
461
|
+
provider = 'local';
|
|
462
|
+
if (providerSelect) providerSelect.value = 'local';
|
|
463
|
+
}
|
|
424
464
|
if (provider === 'local') {
|
|
425
465
|
const isConfigured = !!(statusObj && statusObj.configured);
|
|
426
466
|
elOnboarding.style.display = isConfigured ? 'none' : 'block';
|
|
427
467
|
elRemote.style.display = 'none';
|
|
428
468
|
elChat.style.display = isConfigured ? 'block' : 'none';
|
|
429
469
|
} else {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
470
|
+
// Only show remote setup when cloud is enabled
|
|
471
|
+
if (cloudEnabled) {
|
|
472
|
+
elOnboarding.style.display = 'none';
|
|
473
|
+
elRemote.style.display = 'block';
|
|
474
|
+
elChat.style.display = 'none';
|
|
475
|
+
} else {
|
|
476
|
+
const isConfigured = !!(statusObj && statusObj.configured);
|
|
477
|
+
elOnboarding.style.display = isConfigured ? 'none' : 'block';
|
|
478
|
+
elRemote.style.display = 'none';
|
|
479
|
+
elChat.style.display = isConfigured ? 'block' : 'none';
|
|
480
|
+
}
|
|
433
481
|
}
|
|
434
482
|
// Toggle provider-specific help
|
|
435
483
|
const h11 = document.getElementById('help_openai_1');
|
|
@@ -440,14 +488,16 @@
|
|
|
440
488
|
const c12 = document.getElementById('help_claude_2');
|
|
441
489
|
const g11 = document.getElementById('help_gemini_1');
|
|
442
490
|
const g12 = document.getElementById('help_gemini_2');
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (
|
|
446
|
-
if (
|
|
447
|
-
if (
|
|
448
|
-
if (
|
|
449
|
-
if (
|
|
450
|
-
if (
|
|
491
|
+
var showHelp = function (cond) { return cond ? '' : 'none'; };
|
|
492
|
+
var allowRemoteHelp = cloudEnabled;
|
|
493
|
+
if (h11) h11.style.display = allowRemoteHelp && provider === 'openai' ? '' : 'none';
|
|
494
|
+
if (h12) h12.style.display = allowRemoteHelp && provider === 'openai' ? '' : 'none';
|
|
495
|
+
if (h21) h21.style.display = allowRemoteHelp && provider === 'mistral' ? '' : 'none';
|
|
496
|
+
if (h22) h22.style.display = allowRemoteHelp && provider === 'mistral' ? '' : 'none';
|
|
497
|
+
if (c11) c11.style.display = allowRemoteHelp && provider === 'claude' ? '' : 'none';
|
|
498
|
+
if (c12) c12.style.display = allowRemoteHelp && provider === 'claude' ? '' : 'none';
|
|
499
|
+
if (g11) g11.style.display = allowRemoteHelp && provider === 'gemini' ? '' : 'none';
|
|
500
|
+
if (g12) g12.style.display = allowRemoteHelp && provider === 'gemini' ? '' : 'none';
|
|
451
501
|
}
|
|
452
502
|
|
|
453
503
|
// --- Source pre-prompt helpers ---
|
|
@@ -717,7 +767,14 @@
|
|
|
717
767
|
btnSave && btnSave.addEventListener('click', saveStudio);
|
|
718
768
|
(document.getElementById('btn_save_remote')) && document.getElementById('btn_save_remote').addEventListener('click', saveRemote);
|
|
719
769
|
(document.getElementById('btn_check_remote')) && document.getElementById('btn_check_remote').addEventListener('click', checkRemote);
|
|
720
|
-
providerSelect && providerSelect.addEventListener('change', function () {
|
|
770
|
+
providerSelect && providerSelect.addEventListener('change', function () {
|
|
771
|
+
try {
|
|
772
|
+
var cloudEnabled = !!(lastStatus && lastStatus.cloud_enabled);
|
|
773
|
+
var target = cloudEnabled ? providerSelect.value : 'local';
|
|
774
|
+
if (providerSelect.value !== target) providerSelect.value = target;
|
|
775
|
+
updateProviderUI(target, lastStatus);
|
|
776
|
+
} catch (e) { updateProviderUI('local', lastStatus); }
|
|
777
|
+
});
|
|
721
778
|
send && send.addEventListener('click', sendMessage);
|
|
722
779
|
stop && stop.addEventListener('click', function () { if (streamingController) { try { streamingController.abort(); } catch (e) { } } });
|
|
723
780
|
input && input.addEventListener('keydown', function (e) { if (e.key === 'Enter') sendMessage(); });
|
|
@@ -747,6 +804,8 @@
|
|
|
747
804
|
});
|
|
748
805
|
|
|
749
806
|
renderAgentTabs();
|
|
807
|
+
// Default to local-only providers until status says otherwise
|
|
808
|
+
try { setProviderOptions(false); } catch (e) { }
|
|
750
809
|
refreshStatus();
|
|
751
810
|
checkOllama();
|
|
752
811
|
// Load conversation if present in URL
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
qalita/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
qalita/__main__.py,sha256=mUdL2kKMsr47UP2GIJwBNcDR4yZv-ApcSH-tfcRL03g,2518
|
|
3
3
|
qalita/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
qalita/commands/agent.py,sha256=
|
|
5
|
-
qalita/commands/pack.py,sha256=
|
|
4
|
+
qalita/commands/agent.py,sha256=t4SzLnAOsthAeNU2DCmWEkOexKQb2QQ1xhEzazyMdeM,50660
|
|
5
|
+
qalita/commands/pack.py,sha256=Eu1h-JJEtUBqr6OKPnwE8EuJOQcHpJtZBvKe0XFQaDg,37224
|
|
6
6
|
qalita/commands/source.py,sha256=ObRD2CcDynGbMrmj__FhWvPvR0H6HEn5QSLNR8awQUQ,30945
|
|
7
7
|
qalita/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
qalita/internal/config.py,sha256=P9UU3BH-CIH68-qZjmnk8juzZZ5HJ701hrY6EvrPFOA,4081
|
|
9
9
|
qalita/internal/error_patterns.py,sha256=QDFNZEoTmqVOHkiGFljstY6VOhux7d4p8FIL3pKzwtQ,1447
|
|
10
10
|
qalita/internal/logger.py,sha256=4xWy2BZII89nUw4tKrLXFPOlVGONm8KUBgzX2XZ3u7g,1868
|
|
11
11
|
qalita/internal/request.py,sha256=s-rE9kKRhV9IEveclB2YjAY8FRevKb6qjblM8-PbV20,6209
|
|
12
|
-
qalita/internal/utils.py,sha256=
|
|
12
|
+
qalita/internal/utils.py,sha256=JUrdNlEEbxiBofwIKXnsfV4znIQFfNuj2DgvnsCaSIs,5746
|
|
13
13
|
qalita/web/__init__.py,sha256=NRcQjZSizkqN0a-LutsN7X9LBQ8MhyazXBSuqzUEmXE,62
|
|
14
14
|
qalita/web/app.py,sha256=92M2K7vxDIYaI81D-zvdj4VfCsaTTDs6KJZu6dy5s6U,1323
|
|
15
15
|
qalita/web/blueprints/agents.py,sha256=q_SqdSzzHMsstSzEjHGtq75jFt3wQ6p3atjOSzKgiUw,17367
|
|
@@ -17,7 +17,7 @@ qalita/web/blueprints/context.py,sha256=wIcfBtu3l9qwMe5IPa0VQShELsY3ENPt4Q9OO3L8
|
|
|
17
17
|
qalita/web/blueprints/dashboard.py,sha256=R_1_oAM-Ol0kx5GZkKTaB8c5vjdz-iIK-SGu2RIHE_w,7454
|
|
18
18
|
qalita/web/blueprints/helpers.py,sha256=Hnulyrd-YrHoz2LtGCKCq9MgUeoNoNECTyGDap2DFYI,12724
|
|
19
19
|
qalita/web/blueprints/sources.py,sha256=NqwvzrH9QHARfaXy4ycUXSYpIA8ZkeZULo5-UWOVTWk,34926
|
|
20
|
-
qalita/web/blueprints/studio.py,sha256=
|
|
20
|
+
qalita/web/blueprints/studio.py,sha256=FRsFed38akzccJFLi8Ki85bBECqc_HN99AYhApdSlIo,50656
|
|
21
21
|
qalita/web/public/chatgpt.svg,sha256=pmuiRfxoZ3rhpHpfcm1g1pHkyNWUICgyjcK-s7jOtNw,1739
|
|
22
22
|
qalita/web/public/claude.png,sha256=6ZIrc9WcQ3Wh7ef9nGbVMAwZrsVzmSXoFl34PlbOpdU,1953
|
|
23
23
|
qalita/web/public/favicon.ico,sha256=K0TYoueuDhQ32R3UXXNI31c701EiJ6TpPy7ieb6iXf8,2549
|
|
@@ -89,13 +89,13 @@ qalita/web/templates/navbar.html,sha256=W0eBuH_vrs4GyxkSzxsppOchkgtvnKM-uc07mP5m
|
|
|
89
89
|
qalita/web/templates/sources/added.html,sha256=uTu5jofRi9t9pvWRnc9AtD9B396DvnqDo6HoajVU3DA,1938
|
|
90
90
|
qalita/web/templates/sources/edit.html,sha256=DM5i3Y__MMG0PzJm--OXQSo5PbI4fcRo4UYHm2qGMxA,19723
|
|
91
91
|
qalita/web/templates/sources/select-source.html,sha256=dBTEpCu8KTjmiOG_gBIUFEdGAnGmnV8EFQid7chsVA0,6179
|
|
92
|
-
qalita/web/templates/studio/agent-panel.html,sha256=
|
|
92
|
+
qalita/web/templates/studio/agent-panel.html,sha256=Ygt2PKee1kaJrVs2-TP_-ClyEr3ACb5kdL1-j4PglNY,41521
|
|
93
93
|
qalita/web/templates/studio/context-panel.html,sha256=M8yfYcJQ_2WSwnOLlCTQETzctzL-rjlKLF7PlAUV0PU,15312
|
|
94
94
|
qalita/web/templates/studio/index.html,sha256=NI7zF-4cHQ3id-a_R3GNQ8gQ2Bw4A6liyEZ53AO0QOU,2823
|
|
95
95
|
qalita/web/templates/studio/navbar.html,sha256=IQZ7TFOXv3r5yZmP9f1v7BMUCQU8mOajMV82UPeY3cg,746
|
|
96
96
|
qalita/web/templates/studio/view-panel.html,sha256=JREfFIabpuAZctAzbE_mT_ZDk65MBWevjRNxQUrO8Pw,25198
|
|
97
|
-
qalita-2.3.
|
|
98
|
-
qalita-2.3.
|
|
99
|
-
qalita-2.3.
|
|
100
|
-
qalita-2.3.
|
|
101
|
-
qalita-2.3.
|
|
97
|
+
qalita-2.3.2.dist-info/METADATA,sha256=VfFB9CwnPeo2GCc4VnUNuVShaARGGVvH-Tzun32rBkU,2412
|
|
98
|
+
qalita-2.3.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
99
|
+
qalita-2.3.2.dist-info/entry_points.txt,sha256=RxEByZDKtsRrsDauuINx9XMVP6k26B_CglUmtH206Qo,47
|
|
100
|
+
qalita-2.3.2.dist-info/licenses/LICENSE,sha256=cZt92dnxw87-VK4HB6KnmYV7mpf4JUdBkAHzFn1kQxM,22458
|
|
101
|
+
qalita-2.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|