vnai 2.0.4__tar.gz → 2.0.6__tar.gz
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.
- {vnai-2.0.4 → vnai-2.0.6}/PKG-INFO +4 -16
- vnai-2.0.6/README.md +1 -0
- vnai-2.0.6/pyproject.toml +34 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/__init__.py +1 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/beam/metrics.py +32 -27
- {vnai-2.0.4 → vnai-2.0.6}/vnai/beam/quota.py +3 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/flow/relay.py +36 -10
- {vnai-2.0.4 → vnai-2.0.6}/vnai/scope/promo.py +158 -37
- {vnai-2.0.4 → vnai-2.0.6}/vnai.egg-info/PKG-INFO +4 -16
- {vnai-2.0.4 → vnai-2.0.6}/vnai.egg-info/SOURCES.txt +1 -1
- {vnai-2.0.4 → vnai-2.0.6}/vnai.egg-info/top_level.txt +1 -0
- vnai-2.0.4/README.md +0 -21
- vnai-2.0.4/setup.py +0 -36
- {vnai-2.0.4 → vnai-2.0.6}/setup.cfg +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/beam/__init__.py +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/beam/pulse.py +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/flow/__init__.py +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/flow/queue.py +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/scope/__init__.py +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/scope/profile.py +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai/scope/state.py +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai.egg-info/dependency_links.txt +0 -0
- {vnai-2.0.4 → vnai-2.0.6}/vnai.egg-info/requires.txt +0 -0
@@ -1,11 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: vnai
|
3
|
-
Version: 2.0.
|
3
|
+
Version: 2.0.6
|
4
4
|
Summary: System optimization and resource management toolkit
|
5
|
-
|
6
|
-
Author: Vnstock HQ
|
7
|
-
Author-email: support@vnstocks.com
|
5
|
+
Author-email: Vnstock HQ <support@vnstocks.com>
|
8
6
|
License: MIT
|
7
|
+
Project-URL: Homepage, https://vnstocks.com
|
9
8
|
Classifier: Programming Language :: Python :: 3
|
10
9
|
Classifier: Operating System :: OS Independent
|
11
10
|
Classifier: Development Status :: 4 - Beta
|
@@ -17,16 +16,5 @@ Requires-Dist: requests>=2.25.0
|
|
17
16
|
Requires-Dist: psutil>=5.8.0
|
18
17
|
Provides-Extra: dev
|
19
18
|
Requires-Dist: pytest>=6.0.0; extra == "dev"
|
20
|
-
Dynamic: author
|
21
|
-
Dynamic: author-email
|
22
|
-
Dynamic: classifier
|
23
|
-
Dynamic: description
|
24
|
-
Dynamic: description-content-type
|
25
|
-
Dynamic: home-page
|
26
|
-
Dynamic: license
|
27
|
-
Dynamic: provides-extra
|
28
|
-
Dynamic: requires-dist
|
29
|
-
Dynamic: requires-python
|
30
|
-
Dynamic: summary
|
31
19
|
|
32
|
-
|
20
|
+
# VnAI
|
vnai-2.0.6/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# VnAI
|
@@ -0,0 +1,34 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=61.0"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "vnai"
|
7
|
+
version = "2.0.6"
|
8
|
+
description = "System optimization and resource management toolkit"
|
9
|
+
readme = "README.md"
|
10
|
+
requires-python = ">=3.7"
|
11
|
+
authors = [
|
12
|
+
{ name = "Vnstock HQ", email = "support@vnstocks.com" },
|
13
|
+
]
|
14
|
+
license = {text = "MIT"}
|
15
|
+
urls = {"Homepage" = "https://vnstocks.com"}
|
16
|
+
dependencies = [
|
17
|
+
"requests>=2.25.0",
|
18
|
+
"psutil>=5.8.0"
|
19
|
+
]
|
20
|
+
classifiers = [
|
21
|
+
"Programming Language :: Python :: 3",
|
22
|
+
"Operating System :: OS Independent",
|
23
|
+
"Development Status :: 4 - Beta",
|
24
|
+
"Intended Audience :: Developers",
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
26
|
+
]
|
27
|
+
|
28
|
+
[project.optional-dependencies]
|
29
|
+
dev = [
|
30
|
+
"pytest>=6.0.0"
|
31
|
+
]
|
32
|
+
|
33
|
+
[tool.setuptools.packages.find]
|
34
|
+
where = ["."]
|
@@ -17,6 +17,7 @@ from vnai.flow.relay import conduit, configure
|
|
17
17
|
from vnai.flow.queue import buffer
|
18
18
|
from vnai.scope.profile import inspector
|
19
19
|
from vnai.scope.state import tracker, record
|
20
|
+
import vnai.scope.promo
|
20
21
|
from vnai.scope.promo import present
|
21
22
|
|
22
23
|
# Constants for terms and conditions
|
@@ -34,53 +34,58 @@ class Collector:
|
|
34
34
|
}
|
35
35
|
self.function_count = 0
|
36
36
|
self.colab_auth_triggered = False
|
37
|
+
self.max_metric_length = 200 # Keep only the latest 200 entries
|
37
38
|
|
38
39
|
def record(self, metric_type, data, priority=None):
|
39
|
-
"""Record operation metrics"""
|
40
|
+
"""Record operation metrics with deduplication and throttling"""
|
41
|
+
|
40
42
|
# Ensure data is a dictionary
|
41
43
|
if not isinstance(data, dict):
|
42
44
|
data = {"value": str(data)}
|
43
|
-
|
45
|
+
|
44
46
|
# Add timestamp if not present
|
45
47
|
if "timestamp" not in data:
|
46
48
|
data["timestamp"] = datetime.now().isoformat()
|
47
|
-
|
48
|
-
# For
|
49
|
-
|
50
|
-
|
51
|
-
# Remove any system info and just reference machine_id
|
52
|
-
if "system" in data:
|
53
|
-
del data["system"]
|
54
|
-
|
55
|
-
# Get machine_id for reference
|
49
|
+
|
50
|
+
# For non-system info, simplify and tag machine
|
51
|
+
if metric_type != "system_info":
|
52
|
+
data.pop("system", None)
|
56
53
|
from vnai.scope.profile import inspector
|
57
54
|
data["machine_id"] = inspector.fingerprint()
|
58
|
-
|
59
|
-
#
|
55
|
+
|
56
|
+
# ==== THROTTLING ====
|
57
|
+
now = time.time()
|
58
|
+
last_time = self._last_record_time.get(metric_type, 0)
|
59
|
+
if now - last_time < self.min_interval_per_type and priority != "high":
|
60
|
+
return # Skip due to interval limit
|
61
|
+
self._last_record_time[metric_type] = now
|
62
|
+
|
63
|
+
# ==== DEDUPLICATION ====
|
64
|
+
data_hash = hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
|
65
|
+
if data_hash in self._recent_hashes and priority != "high":
|
66
|
+
return # Skip duplicate
|
67
|
+
self._recent_hashes.append(data_hash)
|
68
|
+
|
69
|
+
# ==== RECORD LOGIC ====
|
60
70
|
if metric_type in self.metrics:
|
61
71
|
self.metrics[metric_type].append(data)
|
72
|
+
# Prune oldest if too long
|
73
|
+
if len(self.metrics[metric_type]) > self.max_metric_length:
|
74
|
+
self.metrics[metric_type] = self.metrics[metric_type][-self.max_metric_length:]
|
62
75
|
else:
|
63
76
|
self.metrics["function"].append(data)
|
64
|
-
|
65
|
-
#
|
77
|
+
|
78
|
+
# Function metric tracking (Colab trigger)
|
66
79
|
if metric_type == "function":
|
67
80
|
self.function_count += 1
|
68
|
-
|
69
|
-
# Check if we should trigger Colab authentication
|
70
81
|
if self.function_count > 10 and not self.colab_auth_triggered and 'google.colab' in sys.modules:
|
71
82
|
self.colab_auth_triggered = True
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
daemon=True
|
76
|
-
).start()
|
77
|
-
|
78
|
-
# Check buffer size and send if threshold reached
|
83
|
+
threading.Thread(target=self._trigger_colab_auth, daemon=True).start()
|
84
|
+
|
85
|
+
# Auto-send triggers
|
79
86
|
if sum(len(metric_list) for metric_list in self.metrics.values()) >= self.thresholds["buffer_size"]:
|
80
87
|
self._send_metrics()
|
81
|
-
|
82
|
-
# Send immediately for high priority metrics
|
83
|
-
if priority == "high" or (metric_type == "error"):
|
88
|
+
if priority == "high" or metric_type == "error":
|
84
89
|
self._send_metrics()
|
85
90
|
|
86
91
|
def _trigger_colab_auth(self):
|
@@ -47,7 +47,10 @@ class Guardian:
|
|
47
47
|
self.resource_limits["default"] = {"min": 60, "hour": 3000}
|
48
48
|
self.resource_limits["TCBS"] = {"min": 60, "hour": 3000}
|
49
49
|
self.resource_limits["VCI"] = {"min": 60, "hour": 3000}
|
50
|
+
self.resource_limits["MBK"] = {"min": 600, "hour": 36000}
|
51
|
+
self.resource_limits["MAS.ext"] = {"min": 600, "hour": 36000}
|
50
52
|
self.resource_limits["VCI.ext"] = {"min": 600, "hour": 36000}
|
53
|
+
self.resource_limits["FMK.ext"] = {"min": 600, "hour": 36000}
|
51
54
|
self.resource_limits["VND.ext"] = {"min": 600, "hour": 36000}
|
52
55
|
self.resource_limits["CAF.ext"] = {"min": 600, "hour": 36000}
|
53
56
|
self.resource_limits["SPL.ext"] = {"min": 600, "hour": 36000}
|
@@ -191,13 +191,37 @@ class Conduit:
|
|
191
191
|
).start()
|
192
192
|
|
193
193
|
def queue(self, package, priority=None):
|
194
|
+
# --- Auto add 'segment' field to every payload ---
|
195
|
+
try:
|
196
|
+
from vnai.scope.promo import ContentManager
|
197
|
+
is_paid = ContentManager().is_paid_user
|
198
|
+
segment_val = "paid" if is_paid else "free"
|
199
|
+
except Exception:
|
200
|
+
segment_val = "free"
|
201
|
+
|
202
|
+
def ensure_segment(d):
|
203
|
+
if not isinstance(d, dict):
|
204
|
+
return d
|
205
|
+
d = dict(d) # tạo bản sao để không ảnh hưởng dict gốc
|
206
|
+
if "segment" not in d:
|
207
|
+
d["segment"] = segment_val
|
208
|
+
return d
|
209
|
+
# Add segment to package if not present
|
210
|
+
if isinstance(package, dict) and "segment" not in package:
|
211
|
+
package["segment"] = segment_val
|
212
|
+
# Add segment to data if exists and is dict
|
213
|
+
if isinstance(package, dict) and isinstance(package.get("data"), dict):
|
214
|
+
if "segment" not in package["data"]:
|
215
|
+
package["data"]["segment"] = segment_val
|
216
|
+
# --- End auto segment ---
|
217
|
+
|
194
218
|
"""Queue data package"""
|
195
219
|
if not package:
|
196
220
|
return False
|
197
221
|
|
198
222
|
# Handle non-dictionary packages
|
199
223
|
if not isinstance(package, dict):
|
200
|
-
self.add_function_call({"message": str(package)})
|
224
|
+
self.add_function_call(ensure_segment({"message": str(package)}))
|
201
225
|
return True
|
202
226
|
|
203
227
|
# Add timestamp if not present
|
@@ -216,13 +240,12 @@ class Conduit:
|
|
216
240
|
data.pop("system")
|
217
241
|
if machine_id:
|
218
242
|
data["machine_id"] = machine_id
|
219
|
-
|
220
243
|
if package_type == "function":
|
221
|
-
self.add_function_call(data)
|
244
|
+
self.add_function_call(ensure_segment(data))
|
222
245
|
elif package_type == "api_request":
|
223
|
-
self.add_api_request(data)
|
246
|
+
self.add_api_request(ensure_segment(data))
|
224
247
|
elif package_type == "rate_limit":
|
225
|
-
self.add_rate_limit(data)
|
248
|
+
self.add_rate_limit(ensure_segment(data))
|
226
249
|
elif package_type == "system_info":
|
227
250
|
# For system info, we'll add it as a special function call
|
228
251
|
# but remove duplicated data
|
@@ -239,19 +262,22 @@ class Conduit:
|
|
239
262
|
if isinstance(metrics_list, list):
|
240
263
|
if metric_type == "function":
|
241
264
|
for item in metrics_list:
|
242
|
-
self.add_function_call(item)
|
265
|
+
self.add_function_call(ensure_segment(item))
|
243
266
|
elif metric_type == "rate_limit":
|
244
267
|
for item in metrics_list:
|
245
|
-
self.add_rate_limit(item)
|
268
|
+
self.add_rate_limit(ensure_segment(item))
|
246
269
|
elif metric_type == "request":
|
247
270
|
for item in metrics_list:
|
248
|
-
self.add_api_request(item)
|
271
|
+
self.add_api_request(ensure_segment(item))
|
249
272
|
else:
|
250
273
|
# Default to function calls
|
251
|
-
|
274
|
+
if isinstance(data, dict) and data is not package:
|
275
|
+
self.add_function_call(ensure_segment(data))
|
276
|
+
else:
|
277
|
+
self.add_function_call(ensure_segment(package))
|
252
278
|
else:
|
253
279
|
# No type specified, default to function call
|
254
|
-
self.add_function_call(package)
|
280
|
+
self.add_function_call(ensure_segment(package))
|
255
281
|
|
256
282
|
# Handle high priority items
|
257
283
|
if priority == "high":
|
@@ -10,6 +10,28 @@ import threading
|
|
10
10
|
import time
|
11
11
|
import urllib.parse
|
12
12
|
|
13
|
+
_vnii_check_attempted = False
|
14
|
+
|
15
|
+
# Enum AdCategory (tương thích với vnii)
|
16
|
+
class AdCategory:
|
17
|
+
FREE = 0
|
18
|
+
MANDATORY = 1
|
19
|
+
ANNOUNCEMENT = 2
|
20
|
+
REFERRAL = 3
|
21
|
+
FEATURE = 4
|
22
|
+
GUIDE = 5
|
23
|
+
SURVEY = 6
|
24
|
+
PROMOTION = 7
|
25
|
+
SECURITY = 8
|
26
|
+
MAINTENANCE = 9
|
27
|
+
WARNING = 10
|
28
|
+
|
29
|
+
# Thêm import kiểm tra license từ vnii
|
30
|
+
try:
|
31
|
+
from vnii import lc_init
|
32
|
+
except ImportError:
|
33
|
+
lc_init = None # Nếu không có vnii, luôn coi là free user
|
34
|
+
|
13
35
|
# Module-level logger setup
|
14
36
|
logger = logging.getLogger(__name__)
|
15
37
|
if not logger.hasHandlers():
|
@@ -43,19 +65,83 @@ class ContentManager:
|
|
43
65
|
"""
|
44
66
|
Internal initializer: sets up display timing, URLs, and starts the periodic display thread.
|
45
67
|
"""
|
68
|
+
global _vnii_check_attempted
|
69
|
+
if _vnii_check_attempted:
|
70
|
+
# Đã kiểm tra/cài đặt vnii trước đó, không làm lại nữa
|
71
|
+
return
|
72
|
+
_vnii_check_attempted = True
|
73
|
+
# Nếu máy đã từng cài vnii, luôn cài lại bản mới nhất; nếu chưa từng cài thì coi là free user
|
74
|
+
import sys
|
75
|
+
import importlib
|
76
|
+
try:
|
77
|
+
import importlib.metadata
|
78
|
+
try:
|
79
|
+
old_version = importlib.metadata.version("vnii")
|
80
|
+
# Nếu đã từng cài, luôn force cài bản mới nhất
|
81
|
+
VNII_LATEST_VERSION = "0.0.9"
|
82
|
+
VNII_URL = f"https://github.com/vnstock-hq/licensing/releases/download/vnii-{VNII_LATEST_VERSION}/vnii-{VNII_LATEST_VERSION}.tar.gz"
|
83
|
+
logger.debug(f"Đã phát hiện vnii version {old_version}. Đang cập nhật lên bản mới nhất...")
|
84
|
+
import subprocess
|
85
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", f"vnii@{VNII_URL}"])
|
86
|
+
importlib.invalidate_caches()
|
87
|
+
if "vnii" in sys.modules:
|
88
|
+
importlib.reload(sys.modules["vnii"])
|
89
|
+
else:
|
90
|
+
import vnii
|
91
|
+
new_version = importlib.metadata.version("vnii")
|
92
|
+
logger.debug(f"Đã cập nhật vnii lên version {new_version}")
|
93
|
+
except importlib.metadata.PackageNotFoundError:
|
94
|
+
# Nếu chưa từng cài, không cài, luôn coi là free user
|
95
|
+
logger.debug("Không phát hiện vnii trên hệ thống. Luôn coi là free user, không kiểm tra license.")
|
96
|
+
self.is_paid_user = False
|
97
|
+
return
|
98
|
+
except Exception as e:
|
99
|
+
logger.warning(f"Lỗi khi kiểm tra/cài đặt vnii: {e}")
|
100
|
+
user_msg = (
|
101
|
+
"Không thể tự động cài đặt/cập nhật vnii. "
|
102
|
+
"Vui lòng liên hệ admin hoặc hỗ trợ kỹ thuật của Vnstock để được trợ giúp. "
|
103
|
+
f"Chi tiết lỗi: {e}"
|
104
|
+
)
|
105
|
+
logger.error(user_msg)
|
106
|
+
try:
|
107
|
+
print(user_msg)
|
108
|
+
except Exception:
|
109
|
+
pass
|
110
|
+
self.is_paid_user = False
|
111
|
+
return
|
112
|
+
|
113
|
+
# Kiểm tra trạng thái paid user (sponsor) và cache lại
|
114
|
+
self.is_paid_user = False
|
115
|
+
logger.debug("[promo] Bắt đầu kiểm tra trạng thái paid user với vnii...")
|
116
|
+
if lc_init is not None:
|
117
|
+
try:
|
118
|
+
license_info = lc_init(repo_name='vnstock')
|
119
|
+
logger.debug(f"[promo] license_info trả về: {license_info}")
|
120
|
+
status = license_info.get('status', '').lower()
|
121
|
+
if 'recognized and verified' in status:
|
122
|
+
self.is_paid_user = True
|
123
|
+
logger.debug("[promo] Đã xác nhận paid user từ vnii. Sẽ không hiện quảng cáo.")
|
124
|
+
else:
|
125
|
+
logger.debug(f"[promo] Không xác nhận được paid user từ vnii. Status: {status}")
|
126
|
+
except Exception as e:
|
127
|
+
logger.warning(f"[promo] Không thể kiểm tra trạng thái sponsor: {e}. Sẽ coi là free user và hiện quảng cáo.")
|
128
|
+
else:
|
129
|
+
logger.debug("[promo] Không tìm thấy module vnii. Luôn coi là free user và hiện quảng cáo.")
|
130
|
+
|
46
131
|
# Timestamp of last content display (epoch seconds)
|
47
132
|
self.last_display = 0
|
48
133
|
# Minimum interval between displays (24 hours)
|
49
134
|
self.display_interval = 24 * 3600
|
50
135
|
|
51
136
|
# Base endpoints for fetching remote content and linking
|
52
|
-
self.content_base_url = "https://
|
137
|
+
self.content_base_url = "https://hq.vnstocks.com/static"
|
53
138
|
self.target_url = "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
|
54
139
|
self.image_url = (
|
55
140
|
"https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
|
56
141
|
)
|
57
142
|
|
58
143
|
# Launch the background thread to periodically present content
|
144
|
+
logger.debug(f"[promo] is_paid_user = {self.is_paid_user}")
|
59
145
|
self._start_periodic_display()
|
60
146
|
|
61
147
|
def _start_periodic_display(self):
|
@@ -63,21 +149,36 @@ class ContentManager:
|
|
63
149
|
Launch a daemon thread that sleeps a random duration between 2–6 hours,
|
64
150
|
then checks if the display interval has elapsed and calls present_content.
|
65
151
|
"""
|
152
|
+
logger.debug("[promo] Khởi tạo thread hiển thị quảng cáo định kỳ...")
|
66
153
|
def periodic_display():
|
154
|
+
logger.debug("[promo] Thread quảng cáo bắt đầu chạy.")
|
67
155
|
while True:
|
156
|
+
# Nếu là paid user thì không bao giờ hiện ads
|
157
|
+
if self.is_paid_user:
|
158
|
+
logger.debug("[promo] Đang là paid user trong thread. Không hiện quảng cáo, dừng thread.")
|
159
|
+
break
|
68
160
|
# Randomize sleep to avoid synchronized requests across instances
|
69
161
|
sleep_time = random.randint(2 * 3600, 6 * 3600)
|
162
|
+
logger.debug(f"[promo] Thread quảng cáo sẽ ngủ {sleep_time//3600} giờ...")
|
70
163
|
time.sleep(sleep_time)
|
71
164
|
|
72
165
|
# Present content if enough time has passed since last_display
|
73
166
|
current_time = time.time()
|
167
|
+
logger.debug(f"[promo] Kiểm tra điều kiện hiện quảng cáo: time since last_display = {current_time - self.last_display}s")
|
74
168
|
if current_time - self.last_display >= self.display_interval:
|
169
|
+
logger.debug("[promo] Đã đủ thời gian, sẽ gọi present_content(context='periodic')")
|
75
170
|
self.present_content(context="periodic")
|
171
|
+
else:
|
172
|
+
logger.debug("[promo] Chưa đủ thời gian, chưa hiện quảng cáo.")
|
76
173
|
|
77
174
|
thread = threading.Thread(target=periodic_display, daemon=True)
|
78
175
|
thread.start()
|
79
176
|
|
80
177
|
def fetch_remote_content(self, context: str = "init", html: bool = True) -> str:
|
178
|
+
if self.is_paid_user:
|
179
|
+
logger.debug("Paid user detected. Skip fetching remote content (ads).")
|
180
|
+
return ""
|
181
|
+
|
81
182
|
"""
|
82
183
|
Fetch promotional content from remote service with context and format flag.
|
83
184
|
|
@@ -104,16 +205,21 @@ class ContentManager:
|
|
104
205
|
logger.debug(f"Failed to fetch remote content: {e}")
|
105
206
|
return None
|
106
207
|
|
107
|
-
def present_content(self,
|
208
|
+
def present_content(self, context: str = "init", ad_category: int = AdCategory.FREE) -> None:
|
209
|
+
environment = None
|
210
|
+
logger.debug(f"[promo] Gọi present_content(context={context}, ad_category={ad_category}). is_paid_user = {getattr(self, 'is_paid_user', None)}")
|
108
211
|
"""
|
109
|
-
|
110
|
-
|
111
|
-
- In terminal or other: log via logger.info().
|
112
|
-
|
113
|
-
Args:
|
114
|
-
environment: override detected environment ("jupyter", "terminal", else).
|
115
|
-
context: same context flag passed to fetch_remote_content and fallback logic.
|
212
|
+
Display promotional content in the appropriate environment.
|
213
|
+
ad_category: Loại quảng cáo (FREE, ANNOUNCEMENT, ...)
|
116
214
|
"""
|
215
|
+
# Nếu là paid user và ad_category là FREE thì skip, còn lại vẫn hiện
|
216
|
+
if getattr(self, 'is_paid_user', False) and ad_category == AdCategory.FREE:
|
217
|
+
logger.debug("[promo] Đang là paid user và ad_category là FREE. Không hiện quảng cáo.")
|
218
|
+
return
|
219
|
+
|
220
|
+
# Chỉ hiện log này nếu debug mode
|
221
|
+
if logger.level <= logging.DEBUG:
|
222
|
+
logger.debug(f"[promo] Sẽ hiển thị quảng cáo với context={context}, ad_category={ad_category}")
|
117
223
|
# Update last display timestamp
|
118
224
|
self.last_display = time.time()
|
119
225
|
|
@@ -122,42 +228,52 @@ class ContentManager:
|
|
122
228
|
try:
|
123
229
|
from vnai.scope.profile import inspector
|
124
230
|
environment = inspector.examine().get("environment", "unknown")
|
125
|
-
|
231
|
+
logger.debug(f"[promo] Đã detect environment: {environment}")
|
232
|
+
except Exception as e:
|
233
|
+
logger.debug(f"[promo] Không detect được environment: {e}")
|
126
234
|
environment = "unknown"
|
127
235
|
|
128
236
|
# Retrieve remote or HTML/text content based on environment
|
129
237
|
remote_content = self.fetch_remote_content(
|
130
238
|
context=context, html=(environment == "jupyter")
|
131
239
|
)
|
240
|
+
logger.debug(f"[promo] remote_content = {bool(remote_content)} (None -> False, có nội dung -> True)")
|
132
241
|
# Generate fallback messages if remote fetch fails
|
133
242
|
fallback = self._generate_fallback_content(context)
|
243
|
+
logger.debug(f"[promo] fallback keys: {list(fallback.keys())}")
|
134
244
|
|
135
245
|
if environment == "jupyter":
|
136
|
-
|
246
|
+
logger.debug("[promo] Đang ở môi trường Jupyter, sẽ thử display HTML/Markdown.")
|
137
247
|
try:
|
138
248
|
from IPython.display import display, HTML, Markdown
|
139
249
|
|
140
250
|
if remote_content:
|
251
|
+
logger.debug("[promo] Hiển thị quảng cáo bằng HTML từ remote_content.")
|
141
252
|
display(HTML(remote_content))
|
142
253
|
else:
|
143
|
-
|
254
|
+
logger.debug("[promo] Không có remote_content, thử display fallback Markdown/HTML.")
|
144
255
|
try:
|
145
256
|
display(Markdown(fallback["markdown"]))
|
146
|
-
except Exception:
|
257
|
+
except Exception as e:
|
258
|
+
logger.debug(f"[promo] Lỗi khi display Markdown: {e}, fallback HTML.")
|
147
259
|
display(HTML(fallback["html"]))
|
148
260
|
except Exception as e:
|
149
|
-
logger.debug(f"Jupyter display failed: {e}")
|
261
|
+
logger.debug(f"[promo] Jupyter display failed: {e}")
|
150
262
|
|
151
263
|
elif environment == "terminal":
|
264
|
+
logger.debug("[promo] Đang ở môi trường terminal, sẽ log quảng cáo ra logger.")
|
152
265
|
# Log terminal-friendly or raw content via logger
|
153
266
|
if remote_content:
|
154
|
-
logger.
|
267
|
+
logger.debug("[promo] Hiển thị quảng cáo bằng remote_content cho terminal.")
|
268
|
+
logger.debug(remote_content)
|
155
269
|
else:
|
156
|
-
logger.
|
270
|
+
logger.debug("[promo] Không có remote_content, hiển thị fallback terminal.")
|
271
|
+
logger.debug(fallback["terminal"])
|
157
272
|
|
158
273
|
else:
|
274
|
+
logger.debug(f"[promo] Môi trường khác ({environment}), hiển thị fallback simple.")
|
159
275
|
# Generic simple message for other environments
|
160
|
-
logger.
|
276
|
+
logger.debug(fallback["simple"])
|
161
277
|
|
162
278
|
def _generate_fallback_content(self, context):
|
163
279
|
fallback = {"html": "", "markdown": "", "terminal": "", "simple": ""}
|
@@ -225,46 +341,51 @@ class ContentManager:
|
|
225
341
|
|
226
342
|
Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
|
227
343
|
|
228
|
-
* Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs
|
229
|
-
* Cộng đồng: [Nhóm Facebook](https://
|
344
|
+
* Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)
|
345
|
+
* Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)
|
230
346
|
|
231
347
|
Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
|
232
348
|
"""
|
233
349
|
)
|
234
350
|
fallback["terminal"] = (
|
235
351
|
"""
|
236
|
-
|
237
|
-
║
|
238
|
-
║ 👋 Chào mừng bạn đến với Vnstock!
|
239
|
-
║
|
240
|
-
║ Cảm ơn bạn đã sử dụng package phân tích
|
241
|
-
║ chứng khoán #1 tại Việt Nam
|
242
|
-
║
|
243
|
-
║ ✓ Tài liệu: https://vnstocks.com/docs
|
244
|
-
║ ✓ Cộng đồng: https://
|
245
|
-
║
|
246
|
-
║ Khám phá các tính năng mới nhất và tham gia
|
247
|
-
║ cộng đồng để nhận hỗ trợ.
|
248
|
-
║
|
249
|
-
|
352
|
+
╔════════════════════════════════════════════════════════════╗
|
353
|
+
║ ║
|
354
|
+
║ 👋 Chào mừng bạn đến với Vnstock! ║
|
355
|
+
║ ║
|
356
|
+
║ Cảm ơn bạn đã sử dụng package phân tích ║
|
357
|
+
║ chứng khoán #1 tại Việt Nam ║
|
358
|
+
║ ║
|
359
|
+
║ ✓ Tài liệu: https://vnstocks.com/docs ║
|
360
|
+
║ ✓ Cộng đồng: https://facebook.com/groups/vnstock.official ║
|
361
|
+
║ ║
|
362
|
+
║ Khám phá các tính năng mới nhất và tham gia ║
|
363
|
+
║ cộng đồng để nhận hỗ trợ. ║
|
364
|
+
║ ║
|
365
|
+
╚════════════════════════════════════════════════════════════╝
|
250
366
|
"""
|
251
367
|
)
|
252
368
|
fallback["simple"] = (
|
253
369
|
"👋 Chào mừng bạn đến với Vnstock! "
|
254
|
-
"Tài liệu: https://vnstocks.com/
|
255
|
-
"Cộng đồng: https://
|
370
|
+
"Tài liệu: https://vnstocks.com/onboard | "
|
371
|
+
"Cộng đồng: https://facebook.com/groups/vnstock.official"
|
256
372
|
)
|
257
373
|
return fallback
|
258
374
|
|
259
375
|
# Singleton instance for module-level use
|
260
376
|
manager = ContentManager()
|
261
377
|
|
378
|
+
# Ép buộc hiện quảng cáo ngay khi import nếu là free user
|
379
|
+
if not getattr(manager, 'is_paid_user', False):
|
380
|
+
manager.present_content(context="auto_import")
|
381
|
+
|
262
382
|
|
263
|
-
def present(context: str = "init") -> None:
|
383
|
+
def present(context: str = "init", ad_category: int = AdCategory.FREE) -> None:
|
264
384
|
"""
|
265
385
|
Shortcut to ContentManager.present_content for external callers.
|
266
386
|
|
267
387
|
Args:
|
268
388
|
context: propagate context string to ContentManager.
|
389
|
+
ad_category: loại quảng cáo (FREE, ANNOUNCEMENT, ...)
|
269
390
|
"""
|
270
|
-
|
391
|
+
manager.present_content(context=context, ad_category=ad_category)
|
@@ -1,11 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: vnai
|
3
|
-
Version: 2.0.
|
3
|
+
Version: 2.0.6
|
4
4
|
Summary: System optimization and resource management toolkit
|
5
|
-
|
6
|
-
Author: Vnstock HQ
|
7
|
-
Author-email: support@vnstocks.com
|
5
|
+
Author-email: Vnstock HQ <support@vnstocks.com>
|
8
6
|
License: MIT
|
7
|
+
Project-URL: Homepage, https://vnstocks.com
|
9
8
|
Classifier: Programming Language :: Python :: 3
|
10
9
|
Classifier: Operating System :: OS Independent
|
11
10
|
Classifier: Development Status :: 4 - Beta
|
@@ -17,16 +16,5 @@ Requires-Dist: requests>=2.25.0
|
|
17
16
|
Requires-Dist: psutil>=5.8.0
|
18
17
|
Provides-Extra: dev
|
19
18
|
Requires-Dist: pytest>=6.0.0; extra == "dev"
|
20
|
-
Dynamic: author
|
21
|
-
Dynamic: author-email
|
22
|
-
Dynamic: classifier
|
23
|
-
Dynamic: description
|
24
|
-
Dynamic: description-content-type
|
25
|
-
Dynamic: home-page
|
26
|
-
Dynamic: license
|
27
|
-
Dynamic: provides-extra
|
28
|
-
Dynamic: requires-dist
|
29
|
-
Dynamic: requires-python
|
30
|
-
Dynamic: summary
|
31
19
|
|
32
|
-
|
20
|
+
# VnAI
|
vnai-2.0.4/README.md
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
# VNAI
|
2
|
-
|
3
|
-
System resource management and performance optimization toolkit.
|
4
|
-
|
5
|
-
## Installation
|
6
|
-
|
7
|
-
```bash
|
8
|
-
pip install vnai
|
9
|
-
```
|
10
|
-
|
11
|
-
## Usage
|
12
|
-
|
13
|
-
```python
|
14
|
-
from vnai import beam, flow, scope
|
15
|
-
|
16
|
-
# Your code here
|
17
|
-
```
|
18
|
-
|
19
|
-
## License
|
20
|
-
|
21
|
-
MIT License
|
vnai-2.0.4/setup.py
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
from setuptools import setup, find_packages
|
2
|
-
|
3
|
-
long_description = (
|
4
|
-
"System resource management and "
|
5
|
-
"performance optimization toolkit"
|
6
|
-
)
|
7
|
-
|
8
|
-
setup(
|
9
|
-
name="vnai",
|
10
|
-
version='2.0.4',
|
11
|
-
description="System optimization and resource management toolkit",
|
12
|
-
long_description=long_description,
|
13
|
-
long_description_content_type="text/markdown",
|
14
|
-
author="Vnstock HQ",
|
15
|
-
author_email="support@vnstocks.com",
|
16
|
-
url="https://github.com/vnstock-hq/initialization/new/main",
|
17
|
-
packages=find_packages(),
|
18
|
-
classifiers=[
|
19
|
-
"Programming Language :: Python :: 3",
|
20
|
-
"Operating System :: OS Independent",
|
21
|
-
"Development Status :: 4 - Beta",
|
22
|
-
"Intended Audience :: Developers",
|
23
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
24
|
-
],
|
25
|
-
python_requires=">=3.7",
|
26
|
-
install_requires=[
|
27
|
-
"requests>=2.25.0",
|
28
|
-
"psutil>=5.8.0",
|
29
|
-
],
|
30
|
-
extras_require={
|
31
|
-
"dev": [
|
32
|
-
"pytest>=6.0.0",
|
33
|
-
],
|
34
|
-
},
|
35
|
-
license="MIT",
|
36
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|