nativelab 0.1.0__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.
Files changed (84) hide show
  1. nativelab/GlobalConfig/__init__.py +0 -0
  2. nativelab/GlobalConfig/binaryResolve.py +34 -0
  3. nativelab/GlobalConfig/config.py +94 -0
  4. nativelab/GlobalConfig/config_global.py +4 -0
  5. nativelab/GlobalConfig/const.py +191 -0
  6. nativelab/GlobalConfig/hardwareUtil.py +38 -0
  7. nativelab/Model/APImodels.py +69 -0
  8. nativelab/Model/ModelRegistry.py +159 -0
  9. nativelab/Model/__init__.py +0 -0
  10. nativelab/Model/model_family.py +107 -0
  11. nativelab/Model/model_global.py +4 -0
  12. nativelab/Model/templates.py +326 -0
  13. nativelab/Prefrences/ParallelLoading.py +30 -0
  14. nativelab/Prefrences/__init__.py +0 -0
  15. nativelab/Prefrences/prefrence_global.py +1 -0
  16. nativelab/Server/ServerHandling.py +287 -0
  17. nativelab/Server/__init__.py +0 -0
  18. nativelab/Server/hfdwld.py +184 -0
  19. nativelab/Server/server_global.py +2 -0
  20. nativelab/UI/Qt6widgets/__init__.py +0 -0
  21. nativelab/UI/Qt6widgets/chatarea.py +142 -0
  22. nativelab/UI/Qt6widgets/chatmodule.py +139 -0
  23. nativelab/UI/Qt6widgets/inputbar.py +175 -0
  24. nativelab/UI/Qt6widgets/messagewidget.py +194 -0
  25. nativelab/UI/Qt6widgets/refrencepanels.py +552 -0
  26. nativelab/UI/Qt6widgets/sessionsidebar.py +141 -0
  27. nativelab/UI/Qt6widgets/thinkingblock.py +79 -0
  28. nativelab/UI/RichTextEditor.py +49 -0
  29. nativelab/UI/UI_const.py +62 -0
  30. nativelab/UI/UI_global.py +7 -0
  31. nativelab/UI/__init__.py +0 -0
  32. nativelab/UI/buildUI.py +677 -0
  33. nativelab/UI/effects.py +45 -0
  34. nativelab/UI/md_to_html.py +198 -0
  35. nativelab/UI/tabs.py +2223 -0
  36. nativelab/UI/widgets.py +7 -0
  37. nativelab/codeparser/__init__.py +0 -0
  38. nativelab/codeparser/codeparser_global.py +2 -0
  39. nativelab/codeparser/parser/__init__.py +0 -0
  40. nativelab/codeparser/parser/parsefinal.py +100 -0
  41. nativelab/codeparser/parser/typeparser.py +41 -0
  42. nativelab/codeparser/refrenceengine.py +308 -0
  43. nativelab/codeparser/scriptparser.py +517 -0
  44. nativelab/components/__init__.py +0 -0
  45. nativelab/components/components_global.py +4 -0
  46. nativelab/components/jobhandler.py +32 -0
  47. nativelab/components/multipdf_summarise.py +290 -0
  48. nativelab/components/pdfsummarise.py +227 -0
  49. nativelab/components/reason_code_pipeline.py +292 -0
  50. nativelab/core/__init__.py +0 -0
  51. nativelab/core/engine_global.py +2 -0
  52. nativelab/core/engines/__init__.py +0 -0
  53. nativelab/core/engines/apiengine.py +98 -0
  54. nativelab/core/engines/llamaengine.py +254 -0
  55. nativelab/core/streamer_global.py +3 -0
  56. nativelab/core/streamerworker/__init__.py +0 -0
  57. nativelab/core/streamerworker/apistreamer.py +114 -0
  58. nativelab/core/streamerworker/clistreamer.py +40 -0
  59. nativelab/core/streamerworker/serverstreamer.py +97 -0
  60. nativelab/icon.ico +0 -0
  61. nativelab/icon.png +0 -0
  62. nativelab/imports/__init__.py +0 -0
  63. nativelab/imports/import_global.py +6 -0
  64. nativelab/imports/optional_lib.py +15 -0
  65. nativelab/imports/pyqt_lib.py +13 -0
  66. nativelab/imports/standard_lib.py +9 -0
  67. nativelab/main.py +2724 -0
  68. nativelab/manual.py +411 -0
  69. nativelab/pipelinebuilder/__init__.py +0 -0
  70. nativelab/pipelinebuilder/blck_typ.py +40 -0
  71. nativelab/pipelinebuilder/canvas.py +912 -0
  72. nativelab/pipelinebuilder/editordialogue.py +509 -0
  73. nativelab/pipelinebuilder/executionWorker.py +932 -0
  74. nativelab/pipelinebuilder/outrender.py +138 -0
  75. nativelab/pipelinebuilder/pipblck.py +54 -0
  76. nativelab/pipelinebuilder/pipe_global.py +8 -0
  77. nativelab/pipelinebuilder/pipebuilder.py +808 -0
  78. nativelab/pipelinebuilder/pipefunctions.py +75 -0
  79. nativelab-0.1.0.dist-info/METADATA +849 -0
  80. nativelab-0.1.0.dist-info/RECORD +84 -0
  81. nativelab-0.1.0.dist-info/WHEEL +5 -0
  82. nativelab-0.1.0.dist-info/entry_points.txt +2 -0
  83. nativelab-0.1.0.dist-info/licenses/LICENSE +661 -0
  84. nativelab-0.1.0.dist-info/top_level.txt +1 -0
File without changes
@@ -0,0 +1,34 @@
1
+ from nativelab.imports.import_global import Path, json, Dict
2
+ from .config import LLAMA_CLI_DEFAULT, LLAMA_SERVER_DEFAULT
3
+ from .const import APP_CONFIG_DEFAULTS, APP_CONFIG_FILE
4
+ def refresh_binary_paths():
5
+ """Re-read SERVER_CONFIG and update module-level LLAMA_CLI / LLAMA_SERVER."""
6
+ from nativelab.Server.server_global import SERVER_CONFIG # local import
7
+
8
+ global LLAMA_CLI, LLAMA_SERVER
9
+ LLAMA_CLI = _resolve_binary(SERVER_CONFIG.cli_path, LLAMA_CLI_DEFAULT)
10
+ LLAMA_SERVER = _resolve_binary(SERVER_CONFIG.server_path, LLAMA_SERVER_DEFAULT)
11
+ MODELS_DIR = Path("./localllm")
12
+ SESSIONS_DIR = Path("./sessions")
13
+ SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
14
+
15
+ def _load_app_config() -> Dict:
16
+ cfg = dict(APP_CONFIG_DEFAULTS)
17
+ if APP_CONFIG_FILE.exists():
18
+ try:
19
+ saved = json.loads(APP_CONFIG_FILE.read_text())
20
+ cfg.update({k: v for k, v in saved.items() if k in cfg})
21
+ except Exception:
22
+ pass
23
+ return cfg
24
+
25
+ def save_app_config(cfg: Dict):
26
+ APP_CONFIG_FILE.write_text(json.dumps(cfg, indent=2))
27
+
28
+ APP_CONFIG = _load_app_config()
29
+
30
+ def _resolve_binary(cfg_path: str, fallback: str) -> str:
31
+ """Return cfg_path if set and exists, else fallback."""
32
+ if cfg_path and Path(cfg_path).exists():
33
+ return cfg_path
34
+ return fallback
@@ -0,0 +1,94 @@
1
+ from nativelab.imports.import_global import Path, _sys, re, os
2
+ _BASE = Path(getattr(_sys, "_MEIPASS", Path(".")))
3
+ _EXT = ".exe" if __import__("platform").system() == "Windows" else ""
4
+
5
+ LLAMA_CLI_DEFAULT = str(_BASE / f"llama-bin/llama-cli{_EXT}")
6
+ LLAMA_SERVER_DEFAULT = str(_BASE / f"llama-bin/llama-server{_EXT}")
7
+
8
+ # Fallback to local llama/bin for dev mode
9
+ if not Path(LLAMA_CLI_DEFAULT).exists():
10
+ LLAMA_CLI_DEFAULT = f"./llama/bin/llama-cli{_EXT}"
11
+ LLAMA_SERVER_DEFAULT = f"./llama/bin/llama-server{_EXT}"
12
+
13
+ # These are resolved at runtime via SERVER_CONFIG (set after ServerConfig loads
14
+ LLAMA_CLI = LLAMA_CLI_DEFAULT
15
+ LLAMA_SERVER = LLAMA_SERVER_DEFAULT
16
+
17
+ DEFAULT_MODEL = "./localllm/mistral-7b-instruct-v0.2.Q5_K_M.gguf"
18
+ def DEFAULT_CTX() -> int:
19
+ return 2048
20
+
21
+ def DEFAULT_THREADS() -> int:
22
+ return os.cpu_count() or 4
23
+ DEFAULT_N_PRED = 512
24
+ CUSTOM_MODELS_FILE = Path("./localllm/custom_models.json")
25
+ MODEL_CONFIGS_FILE = Path("./localllm/model_configs.json")
26
+ PARALLEL_PREFS_FILE = Path("./localllm/parallel_prefs.json")
27
+ SERVER_CONFIG_FILE = Path("./localllm/server_config.json")
28
+ API_MODELS_FILE = Path("./localllm/api_models.json")
29
+
30
+ # ── model roles ───────────────────────────────────────────────────────────────
31
+ MODEL_ROLES = ["general", "reasoning", "summarization", "coding", "secondary"]
32
+ ROLE_ICONS = {
33
+ "general": "💬",
34
+ "reasoning": "🧠",
35
+ "summarization": "📄",
36
+ "coding": "💻",
37
+ "secondary": "🔀",
38
+ }
39
+
40
+ # ═════════════════════════════ MODEL FAMILY DETECTION ═══════════════════════
41
+
42
+ # All GGUF quantization formats supported by llama.cpp
43
+ GGUF_QUANT_PATTERNS = [
44
+ # imatrix importance quants
45
+ r'IQ1_S', r'IQ1_M', r'IQ2_XXS', r'IQ2_XS', r'IQ2_S', r'IQ2_M',
46
+ r'IQ3_XXS', r'IQ3_XS', r'IQ3_S', r'IQ3_M', r'IQ4_XS', r'IQ4_NL',
47
+ # K-quants
48
+ r'Q2_K(?:_S)?', r'Q3_K_(?:S|M|L)', r'Q3_K',
49
+ r'Q4_K_(?:S|M)', r'Q4_K',
50
+ r'Q5_K_(?:S|M)', r'Q5_K',
51
+ r'Q6_K',
52
+ # legacy quants
53
+ r'Q4_0', r'Q4_1', r'Q5_0', r'Q5_1', r'Q8_0',
54
+ # float
55
+ r'F16', r'F32', r'BF16',
56
+ # GGML legacy
57
+ r'Q4_0_4_4', r'Q4_0_4_8', r'Q4_0_8_8',
58
+ ]
59
+
60
+ QUANT_REGEX = re.compile(
61
+ r'\.(' + '|'.join(GGUF_QUANT_PATTERNS) + r')\.gguf$',
62
+ re.IGNORECASE
63
+ )
64
+
65
+ # Dynamic accessors (use these everywhere instead of bare constants)
66
+ def RAM_WATCHDOG_MB() -> float:
67
+ from .binaryResolve import APP_CONFIG
68
+ return float(APP_CONFIG["ram_watchdog_mb"])
69
+
70
+ def CHUNK_INDEX_SIZE() -> int:
71
+ from .binaryResolve import APP_CONFIG
72
+ return int(APP_CONFIG["chunk_index_size"])
73
+
74
+ def MAX_RAM_CHUNKS() -> int:
75
+ from .binaryResolve import APP_CONFIG
76
+ return int(APP_CONFIG["max_ram_chunks"])
77
+
78
+ def get_default_threads() -> int:
79
+ from .binaryResolve import APP_CONFIG
80
+ return int(APP_CONFIG["default_threads"])
81
+
82
+ def get_default_ctx() -> int:
83
+ from .binaryResolve import APP_CONFIG
84
+ return int(APP_CONFIG["default_ctx"])
85
+
86
+ def get_default_n_pred() -> int:
87
+ from .binaryResolve import APP_CONFIG
88
+ return int(APP_CONFIG["default_n_predict"])
89
+ from nativelab.Model.model_global import SCRIPT_LANGUAGES
90
+ SCRIPT_EXTENSIONS_FILTER = (
91
+ "Source files ("
92
+ + " ".join(f"*{ext}" for ext in SCRIPT_LANGUAGES.keys())
93
+ + ");;All Files (*)"
94
+ )
@@ -0,0 +1,4 @@
1
+ from .config import *
2
+ from .binaryResolve import *
3
+ from .const import *
4
+ from .hardwareUtil import *
@@ -0,0 +1,191 @@
1
+ from nativelab.imports.import_global import _sys
2
+ from pathlib import Path
3
+ from .hardwareUtil import cpu_count
4
+ _sys.path.append(str(Path(__file__).resolve().parents[1]))
5
+ # ── Reference system constants ────────────────────────────────────────────────
6
+ REFS_DIR = Path("chat_refs")
7
+ REF_CACHE_DIR = Path("ref_cache")
8
+ REF_INDEX_DIR = Path("ref_index")
9
+ PAUSED_JOBS_DIR = Path("paused_jobs")
10
+ APP_CONFIG_FILE = Path("app_config.json")
11
+ REFS_DIR.mkdir(parents=True, exist_ok=True)
12
+ REF_CACHE_DIR.mkdir(parents=True, exist_ok=True)
13
+ REF_INDEX_DIR.mkdir(parents=True, exist_ok=True)
14
+ PAUSED_JOBS_DIR.mkdir(parents=True, exist_ok=True)
15
+ PIPELINES_DIR = Path.home() / ".native_lab" / "pipelines"
16
+ PIPELINES_DIR.mkdir(parents=True, exist_ok=True)
17
+ APP_CONFIG_DEFAULTS = {
18
+ "ram_watchdog_mb": 800,
19
+ "chunk_index_size": 400,
20
+ "max_ram_chunks": 120,
21
+ "summary_chunk_chars": 3000,
22
+ "summary_ctx_carry": 600,
23
+ "summary_n_pred_sect": 380,
24
+ "summary_n_pred_final": 700,
25
+ "multipdf_n_pred_sect": 380,
26
+ "multipdf_n_pred_final": 900,
27
+ "ref_top_k": 6,
28
+ "ref_max_context_chars": 3000,
29
+ "pause_after_chunks": 2, # auto-pause suggestion threshold
30
+ "default_threads": 12,
31
+ "default_ctx": 4096,
32
+ "default_n_predict": 512,
33
+ "tps_display": True,
34
+ "auto_spill_on_start": False,
35
+ "stream_socket_timeout": 32000, # seconds before a silent read errors
36
+ "stream_stall_timeout": 32000, # seconds of no tokens before giving up
37
+ "stream_max_buf_bytes": 65536, # max line buffer size
38
+ }
39
+ # ─── Config descriptions ──────────────────────────────────────────────────────
40
+ CONFIG_FIELD_META = {
41
+ "ram_watchdog_mb": {
42
+ "label": "RAM Watchdog (MB)",
43
+ "desc": (
44
+ "Free RAM threshold in megabytes. When available RAM drops below "
45
+ "this value, reference chunk caches are automatically spilled to disk "
46
+ "to prevent system memory exhaustion. Lower = more aggressive spilling. "
47
+ "Recommended: 400–1200 MB depending on your total RAM."
48
+ ),
49
+ "min": 100, "max": 8000, "type": "int",
50
+ },
51
+ "chunk_index_size": {
52
+ "label": "Ref Chunk Size (chars)",
53
+ "desc": (
54
+ "Character size of each indexed chunk when a reference file is loaded. "
55
+ "Smaller chunks = finer retrieval granularity but more memory entries. "
56
+ "Larger chunks = broader context per hit but less precise. "
57
+ "Recommended: 300–600."
58
+ ),
59
+ "min": 100, "max": 2000, "type": "int",
60
+ },
61
+ "max_ram_chunks": {
62
+ "label": "Max RAM Chunks (per ref)",
63
+ "desc": (
64
+ "Maximum number of reference chunks kept in RAM per loaded file. "
65
+ "Chunks beyond this limit are evicted to disk. On systems with "
66
+ "8 GB RAM, 80–120 is safe. On 4 GB, consider 40–60."
67
+ ),
68
+ "min": 10, "max": 500, "type": "int",
69
+ },
70
+ "summary_chunk_chars": {
71
+ "label": "Summary Chunk Size (chars)",
72
+ "desc": (
73
+ "Size of each text chunk sent to the model during PDF summarization. "
74
+ "Smaller values = more chunks, less RAM per step, but more inference calls. "
75
+ "Larger values = fewer calls but each prompt uses more context window. "
76
+ "Recommended: 2000–4000."
77
+ ),
78
+ "min": 500, "max": 8000, "type": "int",
79
+ },
80
+ "summary_ctx_carry": {
81
+ "label": "Summary Context Carry (chars)",
82
+ "desc": (
83
+ "Number of characters from the previous chunk's summary to carry forward "
84
+ "as context for the next chunk. Helps maintain narrative continuity "
85
+ "across long documents. Recommended: 400–800."
86
+ ),
87
+ "min": 100, "max": 2000, "type": "int",
88
+ },
89
+ "summary_n_pred_sect": {
90
+ "label": "Summary Tokens / Section",
91
+ "desc": (
92
+ "Maximum tokens the model generates for each section summary. "
93
+ "Higher = more detailed section summaries but slower processing. "
94
+ "Recommended: 250–500."
95
+ ),
96
+ "min": 64, "max": 1024, "type": "int",
97
+ },
98
+ "summary_n_pred_final": {
99
+ "label": "Summary Tokens / Final Pass",
100
+ "desc": (
101
+ "Maximum tokens for the final consolidation pass that synthesizes "
102
+ "all section summaries into one cohesive document summary. "
103
+ "Recommended: 500–900."
104
+ ),
105
+ "min": 128, "max": 2048, "type": "int",
106
+ },
107
+ "multipdf_n_pred_sect": {
108
+ "label": "Multi-PDF Tokens / Section",
109
+ "desc": (
110
+ "Tokens per section for multi-PDF batch summarization. "
111
+ "Same as Summary Tokens / Section but applied to each document "
112
+ "in a multi-document batch job. Recommended: 300–500."
113
+ ),
114
+ "min": 64, "max": 1024, "type": "int",
115
+ },
116
+ "multipdf_n_pred_final": {
117
+ "label": "Multi-PDF Tokens / Final",
118
+ "desc": (
119
+ "Tokens for the final cross-document consolidation in multi-PDF mode. "
120
+ "This pass synthesizes summaries across all loaded documents, "
121
+ "so a higher value gives richer cross-document analysis. "
122
+ "Recommended: 700–1200."
123
+ ),
124
+ "min": 128, "max": 2048, "type": "int",
125
+ },
126
+ "ref_top_k": {
127
+ "label": "Reference Top-K Chunks",
128
+ "desc": (
129
+ "Number of most relevant chunks retrieved from each reference file "
130
+ "per query. Higher = more context injected but larger prompts. "
131
+ "Lower = faster, smaller prompts. Recommended: 4–8."
132
+ ),
133
+ "min": 1, "max": 20, "type": "int",
134
+ },
135
+ "ref_max_context_chars": {
136
+ "label": "Ref Max Context (chars)",
137
+ "desc": (
138
+ "Maximum total characters injected from all references combined "
139
+ "into each prompt. Guards against overflowing the model's context window. "
140
+ "Recommended: 1500–4000."
141
+ ),
142
+ "min": 200, "max": 12000, "type": "int",
143
+ },
144
+ "pause_after_chunks": {
145
+ "label": "Pause-Suggest Threshold (chunks)",
146
+ "desc": (
147
+ "After this many chunks are processed, the app will show a "
148
+ "pause/save banner if many chunks remain. Set to 0 to disable "
149
+ "auto-pause suggestions. Recommended: 2–5."
150
+ ),
151
+ "min": 0, "max": 50, "type": "int",
152
+ },
153
+ "default_threads": {
154
+ "label": "Default CPU Threads",
155
+ "desc": (
156
+ "Number of CPU threads used by llama.cpp for inference. "
157
+ "Setting this higher than your physical core count may reduce performance. "
158
+ f"Your system has {cpu_count()} logical CPUs. "
159
+ "Recommended: physical core count (not hyperthreaded)."
160
+ ),
161
+ "min": 1, "max": 64, "type": "int",
162
+ },
163
+ "default_ctx": {
164
+ "label": "Default Context Window (tokens)",
165
+ "desc": (
166
+ "Default token context window loaded with each model. "
167
+ "Larger context = more conversation history, more RAM. "
168
+ "Each 4096 additional tokens uses ~0.5 GB extra RAM. "
169
+ "Recommended: 2048–8192 for most systems."
170
+ ),
171
+ "min": 512, "max": 32768, "type": "int",
172
+ },
173
+ "default_n_predict": {
174
+ "label": "Default Max New Tokens",
175
+ "desc": (
176
+ "Default maximum number of tokens the model generates per response. "
177
+ "Does not affect RAM usage, only generation length. "
178
+ "Recommended: 256–1024."
179
+ ),
180
+ "min": 32, "max": 4096, "type": "int",
181
+ },
182
+ "auto_spill_on_start": {
183
+ "label": "Auto-Spill Refs on Startup",
184
+ "desc": (
185
+ "When enabled, all reference chunk caches are immediately spilled to disk "
186
+ "on app startup regardless of available RAM. Useful on systems with "
187
+ "very low RAM where you need the model to have maximum headroom."
188
+ ),
189
+ "min": 0, "max": 1, "type": "bool",
190
+ },
191
+ }
@@ -0,0 +1,38 @@
1
+ from nativelab.imports.import_global import hashlib, psutil, HAS_PSUTIL, Dict
2
+
3
+ def ram_free_mb() -> float:
4
+ if HAS_PSUTIL:
5
+ return psutil.virtual_memory().available / (1024 * 1024)
6
+ return 9999.0
7
+
8
+ def cpu_count() -> int:
9
+ try:
10
+ import multiprocessing
11
+ n = multiprocessing.cpu_count()
12
+ return n if n and n > 0 else 4
13
+ except Exception:
14
+ return 4
15
+
16
+ def simple_hash(text: str) -> str:
17
+ return hashlib.md5(text.encode("utf-8", errors="replace")).hexdigest()[:12]
18
+
19
+ _SESSION_REF_STORES: Dict = {}
20
+
21
+ def get_ref_store(session_id: str):
22
+ from codeparser.codeparser_global import SessionReferenceStore
23
+ if session_id not in _SESSION_REF_STORES:
24
+ _SESSION_REF_STORES[session_id] = SessionReferenceStore(session_id)
25
+ return _SESSION_REF_STORES[session_id]
26
+
27
+ class RamWatchdog:
28
+ triggered = False
29
+
30
+ @staticmethod
31
+ def check_and_spill(session_id: str) -> bool:
32
+ from nativelab.GlobalConfig.config_global import RAM_WATCHDOG_MB
33
+ if ram_free_mb() < RAM_WATCHDOG_MB:
34
+ store = get_ref_store(session_id)
35
+ store.flush_ram()
36
+ RamWatchdog.triggered = True
37
+ return True
38
+ return False
@@ -0,0 +1,69 @@
1
+ from nativelab.imports.import_global import dataclass, List, json, Dict
2
+ def _cfg():
3
+ from GlobalConfig import config_global
4
+ return config_global
5
+ @dataclass
6
+ class ApiConfig:
7
+ name: str = ""
8
+ provider: str = "OpenAI"
9
+ model_id: str = ""
10
+ api_key: str = ""
11
+ base_url: str = ""
12
+ api_format: str = "openai"
13
+ max_tokens: int = 2048
14
+ temperature: float = 0.7
15
+ # ── custom prompt format ─────────────────────────────────────────────────
16
+ use_custom_prompt: bool = False
17
+ system_prompt: str = ""
18
+ user_prefix: str = ""
19
+ user_suffix: str = ""
20
+ assistant_prefix: str = ""
21
+ prompt_template: str = "default" # "default"|"chatml"|"llama2"|"alpaca"|"custom"
22
+ # ── custom provider display ──────────────────────────────────────────────
23
+ custom_provider_name: str = ""
24
+
25
+ def to_dict(self) -> Dict:
26
+ return {k: getattr(self, k) for k in self.__dataclass_fields__}
27
+
28
+ @classmethod
29
+ def from_dict(cls, d: Dict) -> "ApiConfig":
30
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
31
+
32
+
33
+ class ApiRegistry:
34
+ def __init__(self):
35
+ self._configs: List[ApiConfig] = []
36
+ self._load()
37
+
38
+ def _load(self):
39
+ if _cfg().API_MODELS_FILE.exists():
40
+ try:
41
+ self._configs = [ApiConfig.from_dict(d)
42
+ for d in json.loads(_cfg().API_MODELS_FILE.read_text())]
43
+ except Exception:
44
+ self._configs = []
45
+
46
+ def save(self):
47
+ _cfg().API_MODELS_FILE.write_text(
48
+ json.dumps([c.to_dict() for c in self._configs], indent=2))
49
+
50
+ def add(self, cfg: ApiConfig):
51
+ self._configs = [c for c in self._configs if c.name != cfg.name]
52
+ self._configs.append(cfg)
53
+ self.save()
54
+
55
+ def remove(self, name: str):
56
+ self._configs = [c for c in self._configs if c.name != name]
57
+ self.save()
58
+
59
+ def all(self) -> List[ApiConfig]:
60
+ return list(self._configs)
61
+
62
+
63
+ api_registry = None
64
+
65
+ def getapi_registry():
66
+ global api_registry
67
+ if api_registry is None:
68
+ api_registry = ApiRegistry()
69
+ return api_registry
@@ -0,0 +1,159 @@
1
+ from nativelab.imports.import_global import Dict, dataclass, Path, json, field
2
+ from .model_family import *
3
+ # ═════════════════════════════ MODEL REGISTRY ═══════════════════════════════
4
+ def _cfg():
5
+ from GlobalConfig import config_global
6
+ return config_global
7
+
8
+ def _default_ctx():
9
+ v = _cfg().DEFAULT_CTX
10
+ return v() if callable(v) else int(v)
11
+
12
+ def _default_threads():
13
+ v = _cfg().DEFAULT_THREADS
14
+ return v() if callable(v) else int(v)
15
+
16
+ def _default_n_pred():
17
+ v = _cfg().DEFAULT_N_PRED
18
+ return v() if callable(v) else int(v)
19
+
20
+ @dataclass
21
+ class ModelConfig:
22
+ path: str
23
+ role: str = "general"
24
+
25
+ threads: int = field(default_factory=_default_threads)
26
+ ctx: int = field(default_factory=_default_ctx)
27
+ n_predict: int = field(default_factory=_default_n_pred)
28
+
29
+ temperature: float = 0.7
30
+ top_p: float = 0.9
31
+ repeat_penalty: float = 1.1
32
+ family: str = "default"
33
+
34
+ def to_dict(self) -> Dict:
35
+ return {
36
+ "path": self.path, "role": self.role,
37
+ "threads": int(self.threads) if not callable(self.threads) else int(self.threads()),
38
+ "ctx": int(self.ctx) if not callable(self.ctx) else int(self.ctx()),
39
+ "n_predict": int(self.n_predict) if not callable(self.n_predict) else int(self.n_predict()),
40
+ "temperature": self.temperature,
41
+ "top_p": self.top_p,
42
+ "repeat_penalty": self.repeat_penalty,
43
+ "family": self.family,
44
+ }
45
+
46
+ @classmethod
47
+ def from_dict(cls, d: Dict) -> "ModelConfig":
48
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
49
+
50
+ @property
51
+ def name(self) -> str:
52
+ return Path(self.path).name
53
+
54
+ @property
55
+ def size_mb(self) -> float:
56
+ p = Path(self.path)
57
+ return round(p.stat().st_size / 1e6, 1) if p.exists() else 0.0
58
+
59
+ @property
60
+ def detected_family(self) -> ModelFamily:
61
+ return detect_model_family(self.path)
62
+
63
+ @property
64
+ def quant_type(self) -> str:
65
+ return detect_quant_type(self.path)
66
+
67
+
68
+ class ModelRegistry:
69
+ def __init__(self):
70
+ self._custom: List[str] = []
71
+ self._configs: Dict[str, ModelConfig] = {}
72
+ self._load()
73
+
74
+ def _load(self):
75
+ from nativelab.GlobalConfig.config_global import DEFAULT_CTX, DEFAULT_THREADS, DEFAULT_N_PRED, CUSTOM_MODELS_FILE, MODEL_CONFIGS_FILE, MODELS_DIR
76
+ if CUSTOM_MODELS_FILE.exists():
77
+ try:
78
+ self._custom = json.loads(CUSTOM_MODELS_FILE.read_text())
79
+ except Exception:
80
+ self._custom = []
81
+ if MODEL_CONFIGS_FILE.exists():
82
+ try:
83
+ raw = json.loads(MODEL_CONFIGS_FILE.read_text())
84
+ self._configs = {p: ModelConfig.from_dict(d) for p, d in raw.items()}
85
+ except Exception:
86
+ self._configs = {}
87
+
88
+ def save(self):
89
+ from nativelab.GlobalConfig.config_global import DEFAULT_CTX, DEFAULT_THREADS, DEFAULT_N_PRED, CUSTOM_MODELS_FILE, MODEL_CONFIGS_FILE, MODELS_DIR
90
+ CUSTOM_MODELS_FILE.parent.mkdir(parents=True, exist_ok=True)
91
+ MODEL_CONFIGS_FILE.parent.mkdir(parents=True, exist_ok=True)
92
+
93
+ CUSTOM_MODELS_FILE.write_text(json.dumps(self._custom, indent=2))
94
+ MODEL_CONFIGS_FILE.write_text(
95
+ json.dumps({p: c.to_dict() for p, c in self._configs.items()}, indent=2))
96
+
97
+ def add(self, path: str):
98
+ if path not in self._custom:
99
+ self._custom.append(path)
100
+ if path not in self._configs:
101
+ fam = detect_model_family(path)
102
+ self._configs[path] = ModelConfig(path=path, family=fam.family)
103
+ self.save()
104
+
105
+ def remove(self, path: str):
106
+ self._custom = [p for p in self._custom if p != path]
107
+ self._configs.pop(path, None)
108
+ self.save()
109
+
110
+ def get_config(self, path: str) -> ModelConfig:
111
+ if path not in self._configs:
112
+ fam = detect_model_family(path)
113
+ self._configs[path] = ModelConfig(path=path, family=fam.family)
114
+ return self._configs[path]
115
+
116
+ def set_config(self, path: str, cfg: ModelConfig):
117
+ self._configs[path] = cfg
118
+ self.save()
119
+
120
+ def all_models(self) -> List[Dict]:
121
+ from nativelab.GlobalConfig.config_global import DEFAULT_CTX, DEFAULT_THREADS, DEFAULT_N_PRED, CUSTOM_MODELS_FILE, MODEL_CONFIGS_FILE, MODELS_DIR
122
+ seen: set = set()
123
+ models: List[Dict] = []
124
+ if MODELS_DIR.exists():
125
+ for f in sorted(MODELS_DIR.glob("*.gguf")):
126
+ seen.add(str(f))
127
+ cfg = self.get_config(str(f))
128
+ fam = detect_model_family(str(f))
129
+ qt = detect_quant_type(str(f))
130
+ models.append({
131
+ "path": str(f), "name": f.name,
132
+ "size_mb": round(f.stat().st_size / 1e6, 1),
133
+ "source": "auto", "role": cfg.role,
134
+ "family": fam.name, "quant": qt,
135
+ })
136
+ for p in self._custom:
137
+ fp = Path(p)
138
+ if fp.exists() and p not in seen:
139
+ seen.add(p)
140
+ cfg = self.get_config(p)
141
+ fam = detect_model_family(p)
142
+ qt = detect_quant_type(p)
143
+ models.append({
144
+ "path": p, "name": fp.name,
145
+ "size_mb": round(fp.stat().st_size / 1e6, 1),
146
+ "source": "custom", "role": cfg.role,
147
+ "family": fam.name, "quant": qt,
148
+ })
149
+ return models
150
+
151
+
152
+ _model_registry = None
153
+
154
+ def get_model_registry():
155
+ global _model_registry
156
+ if _model_registry is None:
157
+ _model_registry = ModelRegistry()
158
+ return _model_registry
159
+
File without changes