ciyidogan commited on
Commit
2392be3
·
verified ·
1 Parent(s): 848cde1

Update config_provider.py

Browse files
Files changed (1) hide show
  1. config_provider.py +645 -0
config_provider.py CHANGED
@@ -525,6 +525,651 @@ class ConfigProvider:
525
 
526
  log("✅ Default environment variables set.")
527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
528
  # Forward references
529
  GlobalConfig.model_rebuild()
530
  VersionConfig.model_rebuild()
 
525
 
526
  log("✅ Default environment variables set.")
527
 
528
+ @classmethod
529
+ def update_user_password(cls, username: str, new_hash: str, new_salt: str) -> None:
530
+ """Update user password"""
531
+ if cls._instance is None:
532
+ cls.get()
533
+
534
+ user = next((u for u in cls._instance.global_config.users if u.username == username), None)
535
+ if user:
536
+ user.password_hash = new_hash
537
+ user.salt = new_salt
538
+ cls._instance.save()
539
+ cls.add_activity_log(username, "CHANGE_PASSWORD", "user", None, username)
540
+
541
+ @classmethod
542
+ def update_environment(cls, update_data: dict, username: str) -> None:
543
+ """Update environment configuration"""
544
+ if cls._instance is None:
545
+ cls.get()
546
+
547
+ config = cls._instance.global_config
548
+
549
+ # Update fields
550
+ if 'work_mode' in update_data:
551
+ config.work_mode = update_data['work_mode']
552
+ if 'cloud_token' in update_data:
553
+ from encryption_utils import encrypt
554
+ config.cloud_token = encrypt(update_data['cloud_token']) if update_data['cloud_token'] else ""
555
+ if 'spark_endpoint' in update_data:
556
+ config.spark_endpoint = update_data['spark_endpoint']
557
+ if 'internal_prompt' in update_data:
558
+ config.internal_prompt = update_data['internal_prompt']
559
+
560
+ # TTS/STT settings
561
+ if 'tts_engine' in update_data:
562
+ config.tts_engine = update_data['tts_engine']
563
+ if 'tts_engine_api_key' in update_data and update_data['tts_engine_api_key'] != "***":
564
+ from encryption_utils import encrypt
565
+ config.tts_engine_api_key = encrypt(update_data['tts_engine_api_key']) if update_data['tts_engine_api_key'] else ""
566
+ if 'tts_settings' in update_data:
567
+ config.tts_settings = update_data['tts_settings']
568
+
569
+ if 'stt_engine' in update_data:
570
+ config.stt_engine = update_data['stt_engine']
571
+ if 'stt_engine_api_key' in update_data:
572
+ from encryption_utils import encrypt
573
+ config.stt_engine_api_key = encrypt(update_data['stt_engine_api_key']) if update_data['stt_engine_api_key'] else ""
574
+ if 'stt_settings' in update_data:
575
+ config.stt_settings = update_data['stt_settings']
576
+
577
+ if 'parameter_collection_config' in update_data:
578
+ config.parameter_collection_config = ParameterCollectionConfig(**update_data['parameter_collection_config'])
579
+
580
+ # Update metadata
581
+ cls._instance.last_update_date = datetime.now().isoformat()
582
+ cls._instance.last_update_user = username
583
+
584
+ # Add activity log
585
+ cls.add_activity_log(username, "UPDATE_ENVIRONMENT", "config", None, "environment",
586
+ f"Changed to {config.work_mode}, TTS: {config.tts_engine}, STT: {config.stt_engine}")
587
+
588
+ # Save to file
589
+ cls._instance.save()
590
+
591
+ # Reload to ensure consistency
592
+ cls.reload()
593
+
594
+ @classmethod
595
+ def add_activity_log(cls, username: str, action: str, entity_type: str,
596
+ entity_id: Any = None, entity_name: str = None, details: str = "") -> None:
597
+ """Add activity log entry"""
598
+ if cls._instance is None:
599
+ cls.get()
600
+
601
+ entry = ActivityLogEntry(
602
+ timestamp=datetime.now().isoformat() + "Z",
603
+ username=username,
604
+ action=action,
605
+ entity_type=entity_type,
606
+ entity_id=entity_id,
607
+ entity_name=entity_name,
608
+ details=details
609
+ )
610
+
611
+ cls._instance.activity_log.append(entry)
612
+
613
+ # Keep only last 1000 entries
614
+ if len(cls._instance.activity_log) > 1000:
615
+ cls._instance.activity_log = cls._instance.activity_log[-1000:]
616
+
617
+ @classmethod
618
+ def get_project(cls, project_id: int) -> Optional[ProjectConfig]:
619
+ """Get project by ID"""
620
+ if cls._instance is None:
621
+ cls.get()
622
+ return next((p for p in cls._instance.projects if p.id == project_id), None)
623
+
624
+ @classmethod
625
+ def get_project_by_name(cls, name: str) -> Optional[ProjectConfig]:
626
+ """Get project by name"""
627
+ if cls._instance is None:
628
+ cls.get()
629
+ return next((p for p in cls._instance.projects if p.name == name), None)
630
+
631
+ @classmethod
632
+ def create_project(cls, project_data: dict, username: str) -> ProjectConfig:
633
+ """Create new project"""
634
+ if cls._instance is None:
635
+ cls.get()
636
+
637
+ # Check duplicate name
638
+ existing = [p.name for p in cls._instance.projects if not getattr(p, 'deleted', False)]
639
+ if project_data['name'] in existing:
640
+ raise ValueError(f"Project name '{project_data['name']}' already exists")
641
+
642
+ # Get new project ID
643
+ project_id = cls._instance.project_id_counter
644
+ cls._instance.project_id_counter += 1
645
+
646
+ # Create project
647
+ new_project = ProjectConfig(
648
+ id=project_id,
649
+ name=project_data['name'],
650
+ caption=project_data.get('caption', ''),
651
+ icon=project_data.get('icon', 'folder'),
652
+ description=project_data.get('description', ''),
653
+ default_language=project_data.get('default_language', 'Türkçe'),
654
+ supported_languages=project_data.get('supported_languages', ['tr-TR']),
655
+ timezone=project_data.get('timezone', 'Europe/Istanbul'),
656
+ region=project_data.get('region', 'tr-TR'),
657
+ enabled=True,
658
+ deleted=False,
659
+ version_id_counter=2, # Start from 2 since we create version 1
660
+ last_version_number=1,
661
+ created_date=datetime.now().isoformat() + "Z",
662
+ created_by=username,
663
+ last_update_date=datetime.now().isoformat() + "Z",
664
+ last_update_user=username,
665
+ versions=[
666
+ VersionConfig(
667
+ id=1,
668
+ version_number=1,
669
+ no=1,
670
+ caption="Version 1",
671
+ published=False,
672
+ created_date=datetime.now().isoformat() + "Z",
673
+ created_by=username,
674
+ last_update_date=datetime.now().isoformat() + "Z",
675
+ last_update_user=username,
676
+ general_prompt="",
677
+ llm=LLMConfig(
678
+ repo_id="Qwen/Qwen2.5-72B-Instruct",
679
+ generation_config={
680
+ "temperature": 0.5,
681
+ "max_tokens": 2048,
682
+ "top_p": 0.7,
683
+ "repetition_penalty": 1.1
684
+ },
685
+ use_fine_tune=False,
686
+ fine_tune_zip=""
687
+ ),
688
+ intents=[]
689
+ )
690
+ ]
691
+ )
692
+
693
+ cls._instance.projects.append(new_project)
694
+
695
+ # Add activity log
696
+ cls.add_activity_log(username, "CREATE_PROJECT", "project", project_id, project_data['name'])
697
+
698
+ # Save
699
+ cls._instance.save()
700
+
701
+ return new_project
702
+
703
+ @classmethod
704
+ def update_project(cls, project_id: int, update_data: dict, username: str) -> ProjectConfig:
705
+ """Update project"""
706
+ if cls._instance is None:
707
+ cls.get()
708
+
709
+ project = cls.get_project(project_id)
710
+ if not project:
711
+ raise ValueError(f"Project {project_id} not found")
712
+
713
+ # Check race condition
714
+ if project.last_update_date != update_data.get('last_update_date'):
715
+ raise ValueError("Project was modified by another user")
716
+
717
+ # Update fields
718
+ project.caption = update_data['caption']
719
+ project.icon = update_data.get('icon', project.icon)
720
+ project.description = update_data.get('description', project.description)
721
+ project.default_language = update_data.get('default_language', project.default_language)
722
+ project.supported_languages = update_data.get('supported_languages', project.supported_languages)
723
+ project.timezone = update_data.get('timezone', project.timezone)
724
+ project.region = update_data.get('region', project.region)
725
+ project.last_update_date = datetime.now().isoformat() + "Z"
726
+ project.last_update_user = username
727
+
728
+ # Add activity log
729
+ cls.add_activity_log(username, "UPDATE_PROJECT", "project", project_id, project.name)
730
+
731
+ # Save
732
+ cls._instance.save()
733
+
734
+ return project
735
+
736
+ @classmethod
737
+ def delete_project(cls, project_id: int, username: str) -> None:
738
+ """Delete project (soft delete)"""
739
+ if cls._instance is None:
740
+ cls.get()
741
+
742
+ project = cls.get_project(project_id)
743
+ if not project:
744
+ raise ValueError(f"Project {project_id} not found")
745
+
746
+ project.deleted = True
747
+ project.last_update_date = datetime.now().isoformat() + "Z"
748
+ project.last_update_user = username
749
+
750
+ # Add activity log
751
+ cls.add_activity_log(username, "DELETE_PROJECT", "project", project_id, project.name)
752
+
753
+ # Save
754
+ cls._instance.save()
755
+
756
+ @classmethod
757
+ def toggle_project(cls, project_id: int, username: str) -> bool:
758
+ """Toggle project enabled status"""
759
+ if cls._instance is None:
760
+ cls.get()
761
+
762
+ project = cls.get_project(project_id)
763
+ if not project:
764
+ raise ValueError(f"Project {project_id} not found")
765
+
766
+ project.enabled = not project.enabled
767
+ project.last_update_date = datetime.now().isoformat() + "Z"
768
+ project.last_update_user = username
769
+
770
+ # Add activity log
771
+ action = "ENABLE_PROJECT" if project.enabled else "DISABLE_PROJECT"
772
+ cls.add_activity_log(username, action, "project", project_id, project.name)
773
+
774
+ # Save
775
+ cls._instance.save()
776
+
777
+ return project.enabled
778
+
779
+ @classmethod
780
+ def create_version(cls, project_id: int, version_data: dict, username: str) -> VersionConfig:
781
+ """Create new version"""
782
+ if cls._instance is None:
783
+ cls.get()
784
+
785
+ project = cls.get_project(project_id)
786
+ if not project:
787
+ raise ValueError(f"Project {project_id} not found")
788
+
789
+ # Get next version ID
790
+ version_id = project.version_id_counter
791
+ project.version_id_counter += 1
792
+
793
+ # Get next version number
794
+ existing_versions = [v for v in project.versions if not getattr(v, 'deleted', False)]
795
+ version_no = max([v.no for v in existing_versions], default=0) + 1
796
+
797
+ # Create base version
798
+ new_version = VersionConfig(
799
+ id=version_id,
800
+ version_number=version_id,
801
+ no=version_no,
802
+ caption=version_data['caption'],
803
+ description=f"Version {version_no}",
804
+ published=False,
805
+ deleted=False,
806
+ created_date=datetime.now().isoformat() + "Z",
807
+ created_by=username,
808
+ last_update_date=datetime.now().isoformat() + "Z",
809
+ last_update_user=username,
810
+ general_prompt="",
811
+ llm=LLMConfig(
812
+ repo_id="",
813
+ generation_config={
814
+ "max_new_tokens": 512,
815
+ "temperature": 0.7,
816
+ "top_p": 0.95,
817
+ "top_k": 50,
818
+ "repetition_penalty": 1.1
819
+ },
820
+ use_fine_tune=False,
821
+ fine_tune_zip=""
822
+ ),
823
+ intents=[]
824
+ )
825
+
826
+ # Copy from source version if specified
827
+ if version_data.get('source_version_id'):
828
+ source_version = next(
829
+ (v for v in project.versions if v.id == version_data['source_version_id']),
830
+ None
831
+ )
832
+ if source_version:
833
+ new_version.general_prompt = source_version.general_prompt
834
+ new_version.llm = source_version.llm.model_copy(deep=True)
835
+ new_version.intents = [i.model_copy(deep=True) for i in source_version.intents]
836
+
837
+ project.versions.append(new_version)
838
+ project.last_update_date = datetime.now().isoformat() + "Z"
839
+ project.last_update_user = username
840
+
841
+ # Add activity log
842
+ cls.add_activity_log(username, "CREATE_VERSION", "version", version_id,
843
+ f"{project.name} v{version_no}")
844
+
845
+ # Save
846
+ cls._instance.save()
847
+
848
+ return new_version
849
+
850
+ @classmethod
851
+ def update_version(cls, project_id: int, version_id: int, update_data: dict, username: str) -> VersionConfig:
852
+ """Update version"""
853
+ if cls._instance is None:
854
+ cls.get()
855
+
856
+ project = cls.get_project(project_id)
857
+ if not project:
858
+ raise ValueError(f"Project {project_id} not found")
859
+
860
+ version = next((v for v in project.versions if v.id == version_id), None)
861
+ if not version:
862
+ raise ValueError(f"Version {version_id} not found")
863
+
864
+ # Check race condition
865
+ if version.last_update_date != update_data.get('last_update_date'):
866
+ raise ValueError("Version was modified by another user")
867
+
868
+ # Cannot update published version
869
+ if version.published:
870
+ raise ValueError("Cannot modify published version")
871
+
872
+ # Update version
873
+ version.caption = update_data['caption']
874
+ version.general_prompt = update_data['general_prompt']
875
+ version.llm = LLMConfig(**update_data['llm'])
876
+ version.intents = [IntentConfig(**i) for i in update_data['intents']]
877
+ version.last_update_date = datetime.now().isoformat() + "Z"
878
+ version.last_update_user = username
879
+
880
+ # Update project timestamp
881
+ project.last_update_date = datetime.now().isoformat() + "Z"
882
+ project.last_update_user = username
883
+
884
+ # Add activity log
885
+ cls.add_activity_log(username, "UPDATE_VERSION", "version", version_id,
886
+ f"{project.name} v{version.no}")
887
+
888
+ # Save
889
+ cls._instance.save()
890
+
891
+ return version
892
+
893
+ @classmethod
894
+ def publish_version(cls, project_id: int, version_id: int, username: str) -> tuple[ProjectConfig, VersionConfig]:
895
+ """Publish version"""
896
+ if cls._instance is None:
897
+ cls.get()
898
+
899
+ project = cls.get_project(project_id)
900
+ if not project:
901
+ raise ValueError(f"Project {project_id} not found")
902
+
903
+ version = next((v for v in project.versions if v.id == version_id), None)
904
+ if not version:
905
+ raise ValueError(f"Version {version_id} not found")
906
+
907
+ # Unpublish all other versions
908
+ for v in project.versions:
909
+ if v.id != version_id:
910
+ v.published = False
911
+
912
+ # Publish this version
913
+ version.published = True
914
+ version.publish_date = datetime.now().isoformat() + "Z"
915
+ version.published_by = username
916
+ version.last_update_date = datetime.now().isoformat() + "Z"
917
+ version.last_update_user = username
918
+
919
+ # Update project timestamp
920
+ project.last_update_date = datetime.now().isoformat() + "Z"
921
+ project.last_update_user = username
922
+
923
+ # Add activity log
924
+ cls.add_activity_log(username, "PUBLISH_VERSION", "version", version_id,
925
+ f"{project.name} v{version.no}")
926
+
927
+ # Save
928
+ cls._instance.save()
929
+
930
+ return project, version
931
+
932
+ @classmethod
933
+ def delete_version(cls, project_id: int, version_id: int, username: str) -> None:
934
+ """Delete version (soft delete)"""
935
+ if cls._instance is None:
936
+ cls.get()
937
+
938
+ project = cls.get_project(project_id)
939
+ if not project:
940
+ raise ValueError(f"Project {project_id} not found")
941
+
942
+ version = next((v for v in project.versions if v.id == version_id), None)
943
+ if not version:
944
+ raise ValueError(f"Version {version_id} not found")
945
+
946
+ # Cannot delete published version
947
+ if version.published:
948
+ raise ValueError("Cannot delete published version")
949
+
950
+ version.deleted = True
951
+ version.last_update_date = datetime.now().isoformat() + "Z"
952
+ version.last_update_user = username
953
+
954
+ project.last_update_date = datetime.now().isoformat() + "Z"
955
+ project.last_update_user = username
956
+
957
+ # Add activity log
958
+ cls.add_activity_log(username, "DELETE_VERSION", "version", version_id,
959
+ f"{project.name} v{version.no}")
960
+
961
+ # Save
962
+ cls._instance.save()
963
+
964
+ @classmethod
965
+ def create_api(cls, api_data: dict, username: str) -> APIConfig:
966
+ """Create new API"""
967
+ if cls._instance is None:
968
+ cls.get()
969
+
970
+ # Check duplicate name
971
+ existing = [a.name for a in cls._instance.apis if not getattr(a, 'deleted', False)]
972
+ if api_data['name'] in existing:
973
+ raise ValueError(f"API name '{api_data['name']}' already exists")
974
+
975
+ # Create API
976
+ new_api = APIConfig(
977
+ **api_data,
978
+ deleted=False,
979
+ created_date=datetime.now().isoformat() + "Z",
980
+ created_by=username,
981
+ last_update_date=datetime.now().isoformat() + "Z",
982
+ last_update_user=username
983
+ )
984
+
985
+ cls._instance.apis.append(new_api)
986
+ cls._instance.build_index() # Rebuild index
987
+
988
+ # Add activity log
989
+ cls.add_activity_log(username, "CREATE_API", "api", None, api_data['name'])
990
+
991
+ # Save
992
+ cls._instance.save()
993
+
994
+ return new_api
995
+
996
+ @classmethod
997
+ def update_api(cls, api_name: str, update_data: dict, username: str) -> APIConfig:
998
+ """Update API"""
999
+ if cls._instance is None:
1000
+ cls.get()
1001
+
1002
+ api = cls._instance.get_api(api_name)
1003
+ if not api:
1004
+ raise ValueError(f"API '{api_name}' not found")
1005
+
1006
+ # Check race condition
1007
+ if api.last_update_date != update_data.get('last_update_date'):
1008
+ raise ValueError("API was modified by another user")
1009
+
1010
+ # Check if API is in use in published versions
1011
+ for project in cls._instance.projects:
1012
+ for version in project.versions:
1013
+ if version.published:
1014
+ for intent in version.intents:
1015
+ if intent.action == api_name:
1016
+ raise ValueError(f"API is used in published version of project '{project.name}'")
1017
+
1018
+ # Update API
1019
+ for key, value in update_data.items():
1020
+ if key != 'last_update_date' and hasattr(api, key):
1021
+ setattr(api, key, value)
1022
+
1023
+ api.last_update_date = datetime.now().isoformat() + "Z"
1024
+ api.last_update_user = username
1025
+
1026
+ # Add activity log
1027
+ cls.add_activity_log(username, "UPDATE_API", "api", None, api_name)
1028
+
1029
+ # Save
1030
+ cls._instance.save()
1031
+
1032
+ return api
1033
+
1034
+ @classmethod
1035
+ def delete_api(cls, api_name: str, username: str) -> None:
1036
+ """Delete API (soft delete)"""
1037
+ if cls._instance is None:
1038
+ cls.get()
1039
+
1040
+ api = cls._instance.get_api(api_name)
1041
+ if not api:
1042
+ raise ValueError(f"API '{api_name}' not found")
1043
+
1044
+ # Check if API is in use
1045
+ for project in cls._instance.projects:
1046
+ if getattr(project, 'deleted', False):
1047
+ continue
1048
+
1049
+ for version in project.versions:
1050
+ if getattr(version, 'deleted', False):
1051
+ continue
1052
+
1053
+ for intent in version.intents:
1054
+ if intent.action == api_name:
1055
+ raise ValueError(f"API is used in intent '{intent.name}' in project '{project.name}' version {version.no}")
1056
+
1057
+ api.deleted = True
1058
+ api.last_update_date = datetime.now().isoformat() + "Z"
1059
+ api.last_update_user = username
1060
+
1061
+ # Add activity log
1062
+ cls.add_activity_log(username, "DELETE_API", "api", None, api_name)
1063
+
1064
+ # Save
1065
+ cls._instance.save()
1066
+
1067
+ @classmethod
1068
+ def import_project(cls, project_data: dict, username: str) -> ProjectConfig:
1069
+ """Import project from JSON"""
1070
+ if cls._instance is None:
1071
+ cls.get()
1072
+
1073
+ # Validate structure
1074
+ if "name" not in project_data:
1075
+ raise ValueError("Invalid project data")
1076
+
1077
+ # Create new project with imported data
1078
+ imported_data = {
1079
+ "name": project_data["name"],
1080
+ "caption": project_data.get("caption", ""),
1081
+ "icon": project_data.get("icon", "folder"),
1082
+ "description": project_data.get("description", "")
1083
+ }
1084
+
1085
+ # Create project
1086
+ new_project = cls.create_project(imported_data, username)
1087
+
1088
+ # Clear default version
1089
+ new_project.versions = []
1090
+
1091
+ # Import versions
1092
+ for idx, version_data in enumerate(project_data.get("versions", [])):
1093
+ new_version = VersionConfig(
1094
+ id=idx + 1,
1095
+ version_number=idx + 1,
1096
+ no=idx + 1,
1097
+ caption=version_data.get("caption", f"Version {idx + 1}"),
1098
+ description=version_data.get("description", ""),
1099
+ published=False,
1100
+ deleted=False,
1101
+ created_date=datetime.now().isoformat() + "Z",
1102
+ created_by=username,
1103
+ last_update_date=datetime.now().isoformat() + "Z",
1104
+ last_update_user=username,
1105
+ general_prompt=version_data.get("general_prompt", ""),
1106
+ llm=LLMConfig(**version_data.get("llm", {})) if version_data.get("llm") else LLMConfig(
1107
+ repo_id="",
1108
+ generation_config={},
1109
+ use_fine_tune=False,
1110
+ fine_tune_zip=""
1111
+ ),
1112
+ intents=[IntentConfig(**i) for i in version_data.get("intents", [])]
1113
+ )
1114
+ new_project.versions.append(new_version)
1115
+ new_project.version_id_counter = idx + 2
1116
+
1117
+ # Add activity log (already added in create_project)
1118
+ cls.add_activity_log(username, "IMPORT_PROJECT", "project", new_project.id, new_project.name)
1119
+
1120
+ # Save
1121
+ cls._instance.save()
1122
+
1123
+ return new_project
1124
+
1125
+ @classmethod
1126
+ def export_project(cls, project_id: int, username: str) -> dict:
1127
+ """Export project as JSON"""
1128
+ if cls._instance is None:
1129
+ cls.get()
1130
+
1131
+ project = cls.get_project(project_id)
1132
+ if not project:
1133
+ raise ValueError(f"Project {project_id} not found")
1134
+
1135
+ # Create export data
1136
+ export_data = {
1137
+ "name": project.name,
1138
+ "caption": project.caption,
1139
+ "icon": project.icon,
1140
+ "description": project.description,
1141
+ "versions": []
1142
+ }
1143
+
1144
+ # Export versions
1145
+ for version in project.versions:
1146
+ if not getattr(version, 'deleted', False):
1147
+ export_version = {
1148
+ "caption": version.caption,
1149
+ "description": getattr(version, 'description', ''),
1150
+ "general_prompt": version.general_prompt,
1151
+ "llm": version.llm.model_dump(),
1152
+ "intents": [i.model_dump() for i in version.intents]
1153
+ }
1154
+ export_data["versions"].append(export_version)
1155
+
1156
+ # Add activity log
1157
+ cls.add_activity_log(username, "EXPORT_PROJECT", "project", project_id, project.name)
1158
+ cls._instance.save()
1159
+
1160
+ return export_data
1161
+
1162
+ @classmethod
1163
+ def cleanup_activity_logs(cls, keep_count: int = 1000) -> None:
1164
+ """Cleanup old activity logs"""
1165
+ if cls._instance is None:
1166
+ cls.get()
1167
+
1168
+ if len(cls._instance.activity_log) > keep_count:
1169
+ # Keep only last entries
1170
+ cls._instance.activity_log = cls._instance.activity_log[-keep_count:]
1171
+ cls._instance.save()
1172
+
1173
  # Forward references
1174
  GlobalConfig.model_rebuild()
1175
  VersionConfig.model_rebuild()