pygpt-net 2.6.65__py3-none-any.whl → 2.6.67__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.
- pygpt_net/CHANGELOG.txt +17 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +2 -0
- pygpt_net/controller/chat/chat.py +0 -0
- pygpt_net/controller/chat/handler/openai_stream.py +137 -7
- pygpt_net/controller/chat/render.py +0 -0
- pygpt_net/controller/config/field/checkbox_list.py +34 -1
- pygpt_net/controller/config/field/textarea.py +2 -2
- pygpt_net/controller/dialogs/info.py +2 -2
- pygpt_net/controller/media/media.py +48 -1
- pygpt_net/controller/model/editor.py +74 -9
- pygpt_net/controller/presets/presets.py +4 -1
- pygpt_net/controller/settings/editor.py +25 -1
- pygpt_net/controller/ui/mode.py +14 -10
- pygpt_net/controller/ui/ui.py +18 -1
- pygpt_net/core/image/image.py +34 -1
- pygpt_net/core/tabs/tabs.py +0 -0
- pygpt_net/core/types/image.py +70 -3
- pygpt_net/core/video/video.py +43 -3
- pygpt_net/data/config/config.json +4 -3
- pygpt_net/data/config/models.json +637 -38
- pygpt_net/data/locale/locale.de.ini +5 -0
- pygpt_net/data/locale/locale.en.ini +5 -0
- pygpt_net/data/locale/locale.es.ini +5 -0
- pygpt_net/data/locale/locale.fr.ini +5 -0
- pygpt_net/data/locale/locale.it.ini +5 -0
- pygpt_net/data/locale/locale.pl.ini +5 -0
- pygpt_net/data/locale/locale.uk.ini +5 -0
- pygpt_net/data/locale/locale.zh.ini +5 -0
- pygpt_net/item/model.py +15 -19
- pygpt_net/provider/agents/openai/agent.py +0 -0
- pygpt_net/provider/api/google/__init__.py +20 -9
- pygpt_net/provider/api/google/image.py +161 -28
- pygpt_net/provider/api/google/video.py +73 -36
- pygpt_net/provider/api/openai/__init__.py +21 -11
- pygpt_net/provider/api/openai/agents/client.py +0 -0
- pygpt_net/provider/api/openai/video.py +562 -0
- pygpt_net/provider/core/config/patch.py +7 -0
- pygpt_net/provider/core/model/patch.py +54 -3
- pygpt_net/provider/vector_stores/qdrant.py +117 -0
- pygpt_net/ui/dialog/models.py +10 -1
- pygpt_net/ui/layout/toolbox/raw.py +7 -1
- pygpt_net/ui/layout/toolbox/video.py +14 -6
- pygpt_net/ui/widget/option/checkbox_list.py +14 -2
- pygpt_net/ui/widget/option/input.py +3 -1
- {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.67.dist-info}/METADATA +72 -25
- {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.67.dist-info}/RECORD +45 -43
- {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.67.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.67.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.67.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
2.6.67 (2025-12-26)
|
|
2
|
+
|
|
3
|
+
- Added a provider filter to the models editor.
|
|
4
|
+
- Added video options (resolution, duration) to the toolbox.
|
|
5
|
+
- Updated the models configuration.
|
|
6
|
+
|
|
7
|
+
2.6.66 (2025-12-25)
|
|
8
|
+
|
|
9
|
+
- Added Sora 2 support - #155.
|
|
10
|
+
- Added Nano Banana support.
|
|
11
|
+
- Added Qdrant Vector Store - merged PR #147 by @Anush008.
|
|
12
|
+
- Added models: gpt-5.2, gpt-image-1.5, gemini-3, nano-banana-pro, sora-2, claude-sonnet-4.5, claude-opus-4.5, veo-3.1.
|
|
13
|
+
- Added Select/unselect All option in checkbox lists.
|
|
14
|
+
- OpenAI SDK upgraded to 2.14.0, Anthropic SDK upgraded to 0.75.0, xAI SDK upgraded to 1.5.0, Google GenAI upgraded to 1.56.0, LlamaIndex upgraded to 0.14.10.
|
|
15
|
+
- Fix: charset-normalizer 3.2.0 circular import - #152.
|
|
16
|
+
- Fix: Google client closed state.
|
|
17
|
+
|
|
1
18
|
2.6.65 (2025-09-28)
|
|
2
19
|
|
|
3
20
|
- Added drag and drop functionality for files and directories from the filesystem in attachments and file explorer.
|
pygpt_net/__init__.py
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.26 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
__author__ = "Marcin Szczygliński"
|
|
13
13
|
__copyright__ = "Copyright 2025, Marcin Szczygliński"
|
|
14
14
|
__credits__ = ["Marcin Szczygliński"]
|
|
15
15
|
__license__ = "MIT"
|
|
16
|
-
__version__ = "2.6.
|
|
17
|
-
__build__ = "2025-
|
|
16
|
+
__version__ = "2.6.67"
|
|
17
|
+
__build__ = "2025-12-26"
|
|
18
18
|
__maintainer__ = "Marcin Szczygliński"
|
|
19
19
|
__github__ = "https://github.com/szczyglis-dev/py-gpt"
|
|
20
20
|
__report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
|
pygpt_net/app.py
CHANGED
|
@@ -146,6 +146,7 @@ from pygpt_net.provider.llms.open_router import OpenRouterLLM
|
|
|
146
146
|
from pygpt_net.provider.vector_stores.chroma import ChromaProvider
|
|
147
147
|
from pygpt_net.provider.vector_stores.elasticsearch import ElasticsearchProvider
|
|
148
148
|
from pygpt_net.provider.vector_stores.pinecode import PinecodeProvider
|
|
149
|
+
from pygpt_net.provider.vector_stores.qdrant import QdrantProvider
|
|
149
150
|
from pygpt_net.provider.vector_stores.redis import RedisProvider
|
|
150
151
|
from pygpt_net.provider.vector_stores.simple import SimpleProvider
|
|
151
152
|
|
|
@@ -475,6 +476,7 @@ def run(**kwargs):
|
|
|
475
476
|
launcher.add_vector_store(ChromaProvider())
|
|
476
477
|
launcher.add_vector_store(ElasticsearchProvider())
|
|
477
478
|
launcher.add_vector_store(PinecodeProvider())
|
|
479
|
+
launcher.add_vector_store(QdrantProvider())
|
|
478
480
|
launcher.add_vector_store(RedisProvider())
|
|
479
481
|
launcher.add_vector_store(SimpleProvider())
|
|
480
482
|
|
|
File without changes
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.26 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import base64
|
|
@@ -17,6 +17,115 @@ from typing import Optional, Any
|
|
|
17
17
|
from .utils import capture_openai_usage
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
# v2: Support both dict and Pydantic objects returned by OpenAI Python SDK v2
|
|
21
|
+
def _to_dict_safe(obj: Any) -> Optional[dict]:
|
|
22
|
+
"""
|
|
23
|
+
Convert OpenAI SDK typed models (Pydantic) or plain objects to dict safely.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
dict or None
|
|
27
|
+
"""
|
|
28
|
+
if obj is None:
|
|
29
|
+
return None
|
|
30
|
+
if isinstance(obj, dict):
|
|
31
|
+
return obj
|
|
32
|
+
# Pydantic v2
|
|
33
|
+
try:
|
|
34
|
+
if hasattr(obj, "model_dump"):
|
|
35
|
+
return obj.model_dump()
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
# Pydantic v1 fallback
|
|
39
|
+
try:
|
|
40
|
+
if hasattr(obj, "dict"):
|
|
41
|
+
return obj.dict()
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
# Generic best-effort
|
|
45
|
+
try:
|
|
46
|
+
return dict(obj)
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
try:
|
|
50
|
+
return getattr(obj, "__dict__", None)
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# v2: Extract nested attribute or dict key chain (e.g. "url_citation.url")
|
|
57
|
+
def _deep_get(obj: Any, path: str, default: Any = None) -> Any:
|
|
58
|
+
"""
|
|
59
|
+
Best-effort nested getter that works for dicts and objects.
|
|
60
|
+
"""
|
|
61
|
+
cur = obj
|
|
62
|
+
for part in path.split("."):
|
|
63
|
+
if cur is None:
|
|
64
|
+
return default
|
|
65
|
+
if isinstance(cur, dict):
|
|
66
|
+
cur = cur.get(part, None)
|
|
67
|
+
else:
|
|
68
|
+
cur = getattr(cur, part, None)
|
|
69
|
+
return cur if cur is not None else default
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# v2: Normalize annotation shape across SDK versions
|
|
73
|
+
def _annotation_type(ann: Any) -> Optional[str]:
|
|
74
|
+
"""
|
|
75
|
+
Return the annotation 'type' in a robust way.
|
|
76
|
+
"""
|
|
77
|
+
t = getattr(ann, "type", None)
|
|
78
|
+
if t:
|
|
79
|
+
return t
|
|
80
|
+
if isinstance(ann, dict):
|
|
81
|
+
return ann.get("type")
|
|
82
|
+
# Try dictified view
|
|
83
|
+
ann_d = _to_dict_safe(ann)
|
|
84
|
+
if isinstance(ann_d, dict):
|
|
85
|
+
return ann_d.get("type")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# v2: Extract URL from url_citation annotation across shapes
|
|
90
|
+
def _extract_url_from_annotation(ann: Any) -> Optional[str]:
|
|
91
|
+
"""
|
|
92
|
+
Supports shapes:
|
|
93
|
+
- {"type":"url_citation","url":"..."}
|
|
94
|
+
- {"type":"url_citation","url_citation":{"url":"..."}}
|
|
95
|
+
- Typed models with attributes: ann.url OR ann.url_citation.url
|
|
96
|
+
"""
|
|
97
|
+
# direct attribute
|
|
98
|
+
url = getattr(ann, "url", None)
|
|
99
|
+
if isinstance(url, str) and url:
|
|
100
|
+
return url
|
|
101
|
+
|
|
102
|
+
# dict direct
|
|
103
|
+
if isinstance(ann, dict):
|
|
104
|
+
url = ann.get("url")
|
|
105
|
+
if isinstance(url, str) and url:
|
|
106
|
+
return url
|
|
107
|
+
nested = ann.get("url_citation")
|
|
108
|
+
if isinstance(nested, dict):
|
|
109
|
+
url = nested.get("url")
|
|
110
|
+
if isinstance(url, str) and url:
|
|
111
|
+
return url
|
|
112
|
+
|
|
113
|
+
# typed nested or generic deep getters
|
|
114
|
+
for candidate in ("url_citation.url", "url_citation.href", "href", "source_url"):
|
|
115
|
+
url = _deep_get(ann, candidate)
|
|
116
|
+
if isinstance(url, str) and url:
|
|
117
|
+
return url
|
|
118
|
+
|
|
119
|
+
# try after dictify
|
|
120
|
+
ann_d = _to_dict_safe(ann)
|
|
121
|
+
if isinstance(ann_d, dict):
|
|
122
|
+
url = ann_d.get("url") or _deep_get(ann_d, "url_citation.url")
|
|
123
|
+
if isinstance(url, str) and url:
|
|
124
|
+
return url
|
|
125
|
+
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
20
129
|
def process_api_chat(ctx, state, chunk) -> Optional[str]:
|
|
21
130
|
"""
|
|
22
131
|
OpenAI-compatible Chat Completions stream delta (robust to dict/object tool_calls).
|
|
@@ -196,17 +305,38 @@ def process_api_chat_responses(ctx, core, state, chunk, etype: Optional[str]) ->
|
|
|
196
305
|
|
|
197
306
|
elif etype == "response.output_text.annotation.added":
|
|
198
307
|
ann = chunk.annotation
|
|
199
|
-
|
|
308
|
+
|
|
309
|
+
# v2: SDK v2 can return a typed model; support both dict and typed
|
|
310
|
+
a_type = _annotation_type(ann)
|
|
311
|
+
|
|
312
|
+
if a_type == "url_citation":
|
|
200
313
|
if state.citations is None:
|
|
201
314
|
state.citations = []
|
|
202
|
-
|
|
203
|
-
|
|
315
|
+
|
|
316
|
+
# Extract URL across shapes and SDK versions
|
|
317
|
+
url_citation = _extract_url_from_annotation(ann)
|
|
318
|
+
|
|
319
|
+
if url_citation and url_citation not in state.citations:
|
|
204
320
|
state.citations.append(url_citation)
|
|
321
|
+
|
|
322
|
+
# keep ctx.urls always reflecting the current list
|
|
205
323
|
ctx.urls = state.citations
|
|
206
|
-
|
|
324
|
+
|
|
325
|
+
elif a_type == "container_file_citation":
|
|
326
|
+
# container-created file (Code Interpreter)
|
|
327
|
+
ann_d = _to_dict_safe(ann) or {}
|
|
328
|
+
state.files.append({
|
|
329
|
+
"container_id": ann_d.get("container_id", _deep_get(ann, "container_id")),
|
|
330
|
+
"file_id": ann_d.get("file_id", _deep_get(ann, "file_id")),
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
elif a_type == "file_citation":
|
|
334
|
+
# v2: Some SDKs emit plain 'file_citation' (non-container). Keep parity with container handling.
|
|
335
|
+
ann_d = _to_dict_safe(ann) or {}
|
|
336
|
+
# optional: store as generic file citation (without container)
|
|
207
337
|
state.files.append({
|
|
208
|
-
"container_id": ann
|
|
209
|
-
"file_id": ann
|
|
338
|
+
"container_id": ann_d.get("container_id", _deep_get(ann, "container_id")), # may be None
|
|
339
|
+
"file_id": ann_d.get("file_id", _deep_get(ann, "file_id")),
|
|
210
340
|
})
|
|
211
341
|
|
|
212
342
|
elif etype == "response.reasoning_summary_text.delta":
|
|
File without changes
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.25 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Any, Dict, List
|
|
@@ -86,6 +86,39 @@ class CheckboxList:
|
|
|
86
86
|
except Exception as e:
|
|
87
87
|
self.window.core.debug.log(e)
|
|
88
88
|
|
|
89
|
+
|
|
90
|
+
def on_select_all(
|
|
91
|
+
self,
|
|
92
|
+
parent_id: str,
|
|
93
|
+
key: str,
|
|
94
|
+
option: dict
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
Event: select all checkboxes
|
|
98
|
+
|
|
99
|
+
:param parent_id: Options parent ID
|
|
100
|
+
:param key: Option key
|
|
101
|
+
:param option: Option data
|
|
102
|
+
"""
|
|
103
|
+
ui = self.window.ui
|
|
104
|
+
cfg_parent = ui.config.get(parent_id)
|
|
105
|
+
if not cfg_parent:
|
|
106
|
+
return
|
|
107
|
+
entry = cfg_parent.get(key)
|
|
108
|
+
if entry is None or not hasattr(entry, "boxes"):
|
|
109
|
+
return
|
|
110
|
+
boxes = entry.boxes
|
|
111
|
+
|
|
112
|
+
mode = "unselect_all"
|
|
113
|
+
|
|
114
|
+
for name, cb in boxes.items():
|
|
115
|
+
if cb is not None and not cb.isChecked():
|
|
116
|
+
mode = "select_all"
|
|
117
|
+
|
|
118
|
+
for name, cb in boxes.items():
|
|
119
|
+
if cb is not None:
|
|
120
|
+
cb.setChecked(mode == "select_all")
|
|
121
|
+
|
|
89
122
|
def get_value(
|
|
90
123
|
self,
|
|
91
124
|
parent_id: str,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Any, Dict
|
|
@@ -38,7 +38,7 @@ class Textarea:
|
|
|
38
38
|
field = parent.get(key)
|
|
39
39
|
if field is None:
|
|
40
40
|
return
|
|
41
|
-
new_text = str(option["value"])
|
|
41
|
+
new_text = str(option["value"]) if "value" in option else ""
|
|
42
42
|
if hasattr(field, "toPlainText"):
|
|
43
43
|
current = field.toPlainText()
|
|
44
44
|
elif hasattr(field, "text"):
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.26 12:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import QUrl
|
|
@@ -101,7 +101,7 @@ class Info:
|
|
|
101
101
|
|
|
102
102
|
def goto_update(self):
|
|
103
103
|
"""Open update URL"""
|
|
104
|
-
self.open_url(self.window.meta['
|
|
104
|
+
self.open_url(self.window.meta['website'])
|
|
105
105
|
|
|
106
106
|
def goto_donate(self):
|
|
107
107
|
"""Open donate page"""
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.26 12:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Any
|
|
@@ -30,6 +30,15 @@ class Media:
|
|
|
30
30
|
else:
|
|
31
31
|
self.window.ui.config['global']['img_raw'].setChecked(False)
|
|
32
32
|
|
|
33
|
+
# mode (image|video|music)
|
|
34
|
+
mode = self.window.core.config.get('img_mode', 'image')
|
|
35
|
+
self.window.controller.config.apply_value(
|
|
36
|
+
parent_id="global",
|
|
37
|
+
key="img_mode",
|
|
38
|
+
option=self.window.core.image.get_mode_option(),
|
|
39
|
+
value=mode,
|
|
40
|
+
)
|
|
41
|
+
|
|
33
42
|
# image: resolution
|
|
34
43
|
resolution = self.window.core.config.get('img_resolution', '1024x1024')
|
|
35
44
|
self.window.controller.config.apply_value(
|
|
@@ -48,10 +57,31 @@ class Media:
|
|
|
48
57
|
value=aspect_ratio,
|
|
49
58
|
)
|
|
50
59
|
|
|
60
|
+
# video: resolution
|
|
61
|
+
resolution = self.window.core.config.get('video.resolution', '720p')
|
|
62
|
+
self.window.controller.config.apply_value(
|
|
63
|
+
parent_id="global",
|
|
64
|
+
key="video.resolution",
|
|
65
|
+
option=self.window.core.video.get_resolution_option(),
|
|
66
|
+
value=resolution,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# video: duration
|
|
70
|
+
duration = self.window.core.config.get('video.duration', 8)
|
|
71
|
+
self.window.controller.config.apply_value(
|
|
72
|
+
parent_id="global",
|
|
73
|
+
key="video.duration",
|
|
74
|
+
option=self.window.core.video.get_duration_option(),
|
|
75
|
+
value=duration,
|
|
76
|
+
)
|
|
77
|
+
|
|
51
78
|
# -- add hooks --
|
|
52
79
|
if not self.initialized:
|
|
53
80
|
self.window.ui.add_hook("update.global.img_resolution", self.hook_update)
|
|
81
|
+
self.window.ui.add_hook("update.global.img_mode", self.hook_update)
|
|
54
82
|
self.window.ui.add_hook("update.global.video.aspect_ratio", self.hook_update)
|
|
83
|
+
self.window.ui.add_hook("update.global.video.resolution", self.hook_update)
|
|
84
|
+
self.window.ui.add_hook("update.global.video.duration", self.hook_update)
|
|
55
85
|
|
|
56
86
|
def reload(self):
|
|
57
87
|
"""Reload UI"""
|
|
@@ -69,10 +99,23 @@ class Media:
|
|
|
69
99
|
if not value:
|
|
70
100
|
return
|
|
71
101
|
self.window.core.config.set('img_resolution', value)
|
|
102
|
+
elif key == "img_mode":
|
|
103
|
+
if not value:
|
|
104
|
+
return
|
|
105
|
+
self.window.core.config.set('img_mode', value)
|
|
106
|
+
self.window.controller.ui.mode.update() # switch image|video options
|
|
72
107
|
elif key == "video.aspect_ratio":
|
|
73
108
|
if not value:
|
|
74
109
|
return
|
|
75
110
|
self.window.core.config.set('video.aspect_ratio', value)
|
|
111
|
+
elif key == "video.resolution":
|
|
112
|
+
if not value:
|
|
113
|
+
return
|
|
114
|
+
self.window.core.config.set('video.resolution', value)
|
|
115
|
+
elif key == "video.duration":
|
|
116
|
+
if not value:
|
|
117
|
+
return
|
|
118
|
+
self.window.core.config.set('video.duration', value)
|
|
76
119
|
|
|
77
120
|
def enable_raw(self):
|
|
78
121
|
"""Enable prompt enhancement for images"""
|
|
@@ -92,6 +135,10 @@ class Media:
|
|
|
92
135
|
else:
|
|
93
136
|
self.enable_raw()
|
|
94
137
|
|
|
138
|
+
def get_mode(self) -> str:
|
|
139
|
+
"""Get media generation mode (image/video/music)"""
|
|
140
|
+
return self.window.core.config.get("img_mode", "image")
|
|
141
|
+
|
|
95
142
|
def is_image_model(self) -> bool:
|
|
96
143
|
"""
|
|
97
144
|
Check if the model is an image generation model
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
@@ -33,6 +33,7 @@ class Editor:
|
|
|
33
33
|
self.height = 500
|
|
34
34
|
self.selected = []
|
|
35
35
|
self.locked = False
|
|
36
|
+
self.provider = "-" # all providers by default
|
|
36
37
|
self.options = {
|
|
37
38
|
"id": {
|
|
38
39
|
"type": "text",
|
|
@@ -137,12 +138,40 @@ class Editor:
|
|
|
137
138
|
if key in self.options:
|
|
138
139
|
return self.options[key]
|
|
139
140
|
|
|
141
|
+
def get_provider_option(self) -> dict:
|
|
142
|
+
"""
|
|
143
|
+
Get provider option
|
|
144
|
+
|
|
145
|
+
:return: provider option
|
|
146
|
+
"""
|
|
147
|
+
return {
|
|
148
|
+
"type": "combo",
|
|
149
|
+
"use": "llm_providers",
|
|
150
|
+
"label": "model.provider",
|
|
151
|
+
"description": "model.provider.desc",
|
|
152
|
+
}
|
|
153
|
+
|
|
140
154
|
def setup(self):
|
|
141
155
|
"""Set up editor"""
|
|
142
156
|
idx = None
|
|
143
157
|
self.window.model_settings.setup(idx) # widget dialog setup
|
|
144
158
|
self.window.ui.add_hook("update.model.name", self.hook_update)
|
|
145
159
|
self.window.ui.add_hook("update.model.mode", self.hook_update)
|
|
160
|
+
self.update_provider(self.provider)
|
|
161
|
+
self.window.ui.add_hook("update.model.provider_global", self.hook_update)
|
|
162
|
+
|
|
163
|
+
def update_provider(self, provider: str):
|
|
164
|
+
"""
|
|
165
|
+
Set provider
|
|
166
|
+
|
|
167
|
+
:param provider: provider name
|
|
168
|
+
"""
|
|
169
|
+
self.window.controller.config.apply_value(
|
|
170
|
+
parent_id="model",
|
|
171
|
+
key="provider_global",
|
|
172
|
+
option=self.get_provider_option(),
|
|
173
|
+
value=provider,
|
|
174
|
+
)
|
|
146
175
|
|
|
147
176
|
def hook_update(
|
|
148
177
|
self,
|
|
@@ -163,6 +192,21 @@ class Editor:
|
|
|
163
192
|
"""
|
|
164
193
|
if self.window.controller.reloading or self.locked:
|
|
165
194
|
return # ignore hooks during reloading process
|
|
195
|
+
|
|
196
|
+
if key == "provider_global":
|
|
197
|
+
# update provider option dynamically
|
|
198
|
+
if self.provider == value:
|
|
199
|
+
return
|
|
200
|
+
self.save(persist=False)
|
|
201
|
+
self.locked = True
|
|
202
|
+
self.current = None
|
|
203
|
+
self.provider = value
|
|
204
|
+
self.reload_items()
|
|
205
|
+
if self.current is None:
|
|
206
|
+
self.init()
|
|
207
|
+
self.locked = False
|
|
208
|
+
return
|
|
209
|
+
|
|
166
210
|
if key in ["id", "name", "mode"]:
|
|
167
211
|
self.save(persist=False)
|
|
168
212
|
self.reload_items()
|
|
@@ -184,6 +228,7 @@ class Editor:
|
|
|
184
228
|
|
|
185
229
|
:param force: force open dialog
|
|
186
230
|
"""
|
|
231
|
+
self.locked = True
|
|
187
232
|
if not self.config_initialized:
|
|
188
233
|
self.setup()
|
|
189
234
|
self.config_initialized = True
|
|
@@ -197,6 +242,8 @@ class Editor:
|
|
|
197
242
|
height=self.height,
|
|
198
243
|
)
|
|
199
244
|
self.dialog = True
|
|
245
|
+
self.window.ui.nodes['models.editor.search'].setFocus() # focus on search
|
|
246
|
+
self.locked = False
|
|
200
247
|
|
|
201
248
|
def undo(self):
|
|
202
249
|
"""Undo last changes in models editor"""
|
|
@@ -219,15 +266,16 @@ class Editor:
|
|
|
219
266
|
self.window.core.models.sort_items()
|
|
220
267
|
self.reload_items()
|
|
221
268
|
|
|
222
|
-
# select the first
|
|
269
|
+
# select the first model on list if no model selected yet
|
|
270
|
+
items = self.prepare_items()
|
|
223
271
|
if self.current is None:
|
|
224
|
-
if len(
|
|
225
|
-
self.current = list(
|
|
272
|
+
if len(items) > 0:
|
|
273
|
+
self.current = list(items.keys())[0]
|
|
226
274
|
|
|
227
275
|
# assign model options to config dialog fields
|
|
228
276
|
options = copy.deepcopy(self.get_options()) # copy options
|
|
229
|
-
if self.current in
|
|
230
|
-
model =
|
|
277
|
+
if self.current in items:
|
|
278
|
+
model = items[self.current]
|
|
231
279
|
data_dict = model.to_dict()
|
|
232
280
|
for key in options:
|
|
233
281
|
if key in data_dict:
|
|
@@ -237,7 +285,7 @@ class Editor:
|
|
|
237
285
|
# custom fields
|
|
238
286
|
options["extra_json"]["value"] = json.dumps(model.extra, indent=4) if model.extra else ""
|
|
239
287
|
|
|
240
|
-
if self.current is not None and self.current in
|
|
288
|
+
if self.current is not None and self.current in items:
|
|
241
289
|
self.set_tab_by_id(self.current)
|
|
242
290
|
|
|
243
291
|
# load and apply options to config dialog
|
|
@@ -313,10 +361,26 @@ class Editor:
|
|
|
313
361
|
event = Event(Event.MODELS_CHANGED)
|
|
314
362
|
self.window.dispatch(event, all=True)
|
|
315
363
|
|
|
364
|
+
def prepare_items(self) -> dict:
|
|
365
|
+
"""
|
|
366
|
+
Prepare items by provider
|
|
367
|
+
|
|
368
|
+
:return: items by provider
|
|
369
|
+
"""
|
|
370
|
+
items = self.window.core.models.items
|
|
371
|
+
if self.provider == "-":
|
|
372
|
+
return items # all providers
|
|
373
|
+
items_by_provider = {}
|
|
374
|
+
for model_id, model in items.items():
|
|
375
|
+
provider = model.provider
|
|
376
|
+
if provider != self.provider:
|
|
377
|
+
continue
|
|
378
|
+
items_by_provider[model_id] = model
|
|
379
|
+
return items_by_provider
|
|
380
|
+
|
|
316
381
|
def reload_items(self):
|
|
317
382
|
"""Reload items"""
|
|
318
|
-
|
|
319
|
-
self.window.model_settings.update_list("models.list", items)
|
|
383
|
+
self.window.model_settings.update_list("models.list", self.prepare_items())
|
|
320
384
|
|
|
321
385
|
def select(self, idx: int):
|
|
322
386
|
"""Select model"""
|
|
@@ -331,6 +395,7 @@ class Editor:
|
|
|
331
395
|
self.locked = True
|
|
332
396
|
self.save(persist=False)
|
|
333
397
|
model = self.window.core.models.create_empty()
|
|
398
|
+
model.provider = self.provider
|
|
334
399
|
self.window.core.models.sort_items()
|
|
335
400
|
self.window.core.models.save()
|
|
336
401
|
self.reload_items()
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.25 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import re
|
|
@@ -497,8 +497,10 @@ class Presets:
|
|
|
497
497
|
|
|
498
498
|
:param no_scroll: do not scroll to current
|
|
499
499
|
"""
|
|
500
|
+
self.locked = True
|
|
500
501
|
w = self.window
|
|
501
502
|
if w.core.config.get('mode') == MODE_ASSISTANT:
|
|
503
|
+
self.locked = False
|
|
502
504
|
return
|
|
503
505
|
if no_scroll:
|
|
504
506
|
w.ui.nodes['preset.presets'].store_scroll_position()
|
|
@@ -511,6 +513,7 @@ class Presets:
|
|
|
511
513
|
if no_scroll:
|
|
512
514
|
w.ui.nodes['preset.presets'].restore_scroll_position()
|
|
513
515
|
self.on_changed()
|
|
516
|
+
self.locked = False
|
|
514
517
|
|
|
515
518
|
def update_list(self):
|
|
516
519
|
"""Update presets list"""
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.12.26 12:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
@@ -206,6 +206,30 @@ class Editor:
|
|
|
206
206
|
if self.config_changed('access.shortcuts'):
|
|
207
207
|
self.window.setup_global_shortcuts()
|
|
208
208
|
|
|
209
|
+
# video: resolution
|
|
210
|
+
if self.config_changed('video.resolution'):
|
|
211
|
+
value = self.window.core.config.get('video.resolution')
|
|
212
|
+
self.window.core.config.set('video.resolution', value)
|
|
213
|
+
option = self.window.core.video.get_resolution_option()
|
|
214
|
+
self.window.controller.config.apply_value(
|
|
215
|
+
parent_id='global',
|
|
216
|
+
key='video.resolution',
|
|
217
|
+
option=option,
|
|
218
|
+
value=str(value),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# video: duration
|
|
222
|
+
if self.config_changed('video.duration'):
|
|
223
|
+
value = self.window.core.config.get('video.duration')
|
|
224
|
+
self.window.core.config.set('video.duration', value)
|
|
225
|
+
option = self.window.core.video.get_duration_option()
|
|
226
|
+
self.window.controller.config.apply_value(
|
|
227
|
+
parent_id='global',
|
|
228
|
+
key='video.duration',
|
|
229
|
+
option=option,
|
|
230
|
+
value=int(value) or 8,
|
|
231
|
+
)
|
|
232
|
+
|
|
209
233
|
# update ENV
|
|
210
234
|
self.window.core.config.setup_env()
|
|
211
235
|
|