p-ai.token 0.1.0__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.
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ recursive-include thonnycontrib *.py api.txt api.txt.example
3
+ global-exclude *.py[cod]
4
+
5
+ include thonnycontrib/api.txt thonnycontrib/api.txt.example
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: p-ai.token
3
+ Version: 0.1.0
4
+ Summary: Experimental AI request plugin for Thonny
5
+ Keywords: thonny,plugin,ai
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: thonny<6.0,>=4.0
9
+
10
+ # Thonny AI Request — FIX 7
11
+
12
+ ## Cài đặt
13
+ 1. Đóng hoàn toàn Thonny.
14
+ 2. Giải nén ZIP.
15
+ 3. (Tuỳ chọn) Thêm API key vào `thonnycontrib/api.txt`, mỗi key một dòng.
16
+ 4. Chạy `install.cmd`.
17
+ 5. Mở lại Thonny.
18
+
19
+ Installer dọn bản cũ của riêng extension rồi cài bản mới. Nếu `api.txt` trong ZIP có key, file đó được chép vào thư mục cài đặt. Nếu file chỉ có chú thích, key đã cài trên máy sẽ được giữ nguyên; nếu chưa có file thì template được chép vào. Đọc thông báo của installer để biết đường dẫn cấu hình thực tế.
20
+
21
+ Thử chạy trong một file Python:
22
+ ```python
23
+ request("Xin chào! Trả lời bằng tiếng Việt.")
24
+ ```
25
+
26
+ Khi thành công, Shell chỉ hiển thị link `Click here` màu `#F2F2F2`, gạch chân. Nhấn link để chép câu trả lời vào clipboard. Câu trả lời không được in trực tiếp ra Shell.
27
+
28
+ Ghi chú: đây là prototype; cần xác nhận tương thích với phiên bản Thonny đang cài.
@@ -0,0 +1,19 @@
1
+ # Thonny AI Request — FIX 7
2
+
3
+ ## Cài đặt
4
+ 1. Đóng hoàn toàn Thonny.
5
+ 2. Giải nén ZIP.
6
+ 3. (Tuỳ chọn) Thêm API key vào `thonnycontrib/api.txt`, mỗi key một dòng.
7
+ 4. Chạy `install.cmd`.
8
+ 5. Mở lại Thonny.
9
+
10
+ Installer dọn bản cũ của riêng extension rồi cài bản mới. Nếu `api.txt` trong ZIP có key, file đó được chép vào thư mục cài đặt. Nếu file chỉ có chú thích, key đã cài trên máy sẽ được giữ nguyên; nếu chưa có file thì template được chép vào. Đọc thông báo của installer để biết đường dẫn cấu hình thực tế.
11
+
12
+ Thử chạy trong một file Python:
13
+ ```python
14
+ request("Xin chào! Trả lời bằng tiếng Việt.")
15
+ ```
16
+
17
+ Khi thành công, Shell chỉ hiển thị link `Click here` màu `#F2F2F2`, gạch chân. Nhấn link để chép câu trả lời vào clipboard. Câu trả lời không được in trực tiếp ra Shell.
18
+
19
+ Ghi chú: đây là prototype; cần xác nhận tương thích với phiên bản Thonny đang cài.
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: p-ai.token
3
+ Version: 0.1.0
4
+ Summary: Experimental AI request plugin for Thonny
5
+ Keywords: thonny,plugin,ai
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: thonny<6.0,>=4.0
9
+
10
+ # Thonny AI Request — FIX 7
11
+
12
+ ## Cài đặt
13
+ 1. Đóng hoàn toàn Thonny.
14
+ 2. Giải nén ZIP.
15
+ 3. (Tuỳ chọn) Thêm API key vào `thonnycontrib/api.txt`, mỗi key một dòng.
16
+ 4. Chạy `install.cmd`.
17
+ 5. Mở lại Thonny.
18
+
19
+ Installer dọn bản cũ của riêng extension rồi cài bản mới. Nếu `api.txt` trong ZIP có key, file đó được chép vào thư mục cài đặt. Nếu file chỉ có chú thích, key đã cài trên máy sẽ được giữ nguyên; nếu chưa có file thì template được chép vào. Đọc thông báo của installer để biết đường dẫn cấu hình thực tế.
20
+
21
+ Thử chạy trong một file Python:
22
+ ```python
23
+ request("Xin chào! Trả lời bằng tiếng Việt.")
24
+ ```
25
+
26
+ Khi thành công, Shell chỉ hiển thị link `Click here` màu `#F2F2F2`, gạch chân. Nhấn link để chép câu trả lời vào clipboard. Câu trả lời không được in trực tiếp ra Shell.
27
+
28
+ Ghi chú: đây là prototype; cần xác nhận tương thích với phiên bản Thonny đang cài.
@@ -0,0 +1,12 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ p_ai.token.egg-info/PKG-INFO
5
+ p_ai.token.egg-info/SOURCES.txt
6
+ p_ai.token.egg-info/dependency_links.txt
7
+ p_ai.token.egg-info/requires.txt
8
+ p_ai.token.egg-info/top_level.txt
9
+ thonnycontrib/api.txt
10
+ thonnycontrib/api.txt.example
11
+ thonnycontrib/thonny_ai_request.py
12
+ thonnycontrib/backend/ai_request_backend.py
@@ -0,0 +1 @@
1
+ thonny<6.0,>=4.0
@@ -0,0 +1 @@
1
+ thonnycontrib
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "p-ai.token"
7
+ version = "0.1.0"
8
+ description = "Experimental AI request plugin for Thonny"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ dependencies = ["thonny>=4.0,<6.0"]
12
+ keywords = ["thonny", "plugin", "ai"]
13
+
14
+ [tool.setuptools]
15
+ include-package-data = true
16
+
17
+ [tool.setuptools.packages.find]
18
+ where = ["."]
19
+ include = ["thonnycontrib*"]
20
+ namespaces = true
21
+
22
+ [tool.setuptools.package-data]
23
+ thonnycontrib = ["api.txt", "api.txt.example"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ sk_nJlyXMeRJFUI8EJk5H5V8j9uXddldDMu
2
+ sk_qxg2tMIqr6gJwuoknc2ofSrxa9cXk0Ne
3
+ sk_GQdRJ7mgL0xbPMdhDMh12OfIWuGPatVm
4
+ sk_a9d9sppsznkDooyGXBK6hgllBJr2WSIF
5
+ sk_TZNHaNNvM6aNjPdwOXLyC6iWFxpEic0V
6
+ sk_KpgxKTD5CVYlTCAxkUTAez6UvGJJMfAT
7
+ sk_PCAiWc2rZ4aU9RuMM7TNVv5fCK0qHTjj
@@ -0,0 +1 @@
1
+ # Add one RevidAPI key per line to a local api.txt file after installation.
@@ -0,0 +1,107 @@
1
+ """Backend plugin implementing request(question) for Thonny scripts."""
2
+ import base64
3
+ import builtins
4
+ import json
5
+ import sys
6
+ import urllib.request
7
+ from pathlib import Path
8
+
9
+ MODEL = "claude-sonnet-5"
10
+ API_URL = "https://revidapi.com/v1/chat/completions"
11
+ MAX_TOKENS = 1200
12
+ TIMEOUT_SECONDS = 45
13
+ START = "__THONNY_AI_REQUEST_BEGIN__"
14
+ END = "__THONNY_AI_REQUEST_END__"
15
+
16
+
17
+ def _read_keys():
18
+ key_file = Path(__file__).resolve().parents[1] / "api.txt"
19
+ try:
20
+ lines = key_file.read_text(encoding="utf-8-sig").splitlines()
21
+ except Exception:
22
+ return []
23
+ return [line.strip() for line in lines if line.strip() and not line.lstrip().startswith("#")]
24
+
25
+
26
+ def _extract_text(data):
27
+ try:
28
+ content = data["choices"][0]["message"]["content"]
29
+ except (KeyError, IndexError, TypeError):
30
+ return ""
31
+ if isinstance(content, str):
32
+ return content.strip()
33
+ if isinstance(content, list):
34
+ parts = []
35
+ for block in content:
36
+ if isinstance(block, dict) and isinstance(block.get("text"), str):
37
+ parts.append(block["text"])
38
+ return "".join(parts).strip()
39
+ return ""
40
+
41
+
42
+ def _ask_api(question, keys):
43
+ payload = json.dumps(
44
+ {
45
+ "model": MODEL,
46
+ "messages": [
47
+ {"role": "system", "content": "Answer clearly and concisely in the language used by the user."},
48
+ {"role": "user", "content": question},
49
+ ],
50
+ "max_tokens": MAX_TOKENS,
51
+ "temperature": 0.3,
52
+ },
53
+ ensure_ascii=False,
54
+ ).encode("utf-8")
55
+
56
+ for key in keys:
57
+ try:
58
+ req = urllib.request.Request(
59
+ API_URL,
60
+ data=payload,
61
+ method="POST",
62
+ headers={
63
+ "Authorization": "Bearer " + key,
64
+ "X-API-Key": key,
65
+ "Content-Type": "application/json",
66
+ },
67
+ )
68
+ with urllib.request.urlopen(req, timeout=TIMEOUT_SECONDS) as response:
69
+ data = json.loads(response.read().decode("utf-8"))
70
+ answer = _extract_text(data)
71
+ if answer:
72
+ return answer
73
+ except Exception:
74
+ # As requested, silently try the next API key.
75
+ continue
76
+ return None
77
+
78
+
79
+ def request(question):
80
+ """Ask RevidAPI, print the answer and an in-Shell copy link."""
81
+ try:
82
+ prompt = question if isinstance(question, str) else str(question)
83
+ keys = _read_keys()
84
+ if not keys:
85
+ print("[AI Request] Chưa có API key. Thêm key vào thonnycontrib/api.txt.")
86
+ return None
87
+
88
+ answer = _ask_api(prompt, keys)
89
+ if not answer:
90
+ print("[AI Request] Không nhận được câu trả lời. Hãy kiểm tra API key, credit và model trong api.txt.")
91
+ return None
92
+
93
+ encoded = base64.b64encode(answer.encode("utf-8")).decode("ascii")
94
+ # Only emit the private marker. The frontend turns it into the
95
+ # clickable "Click here" link; never print the natural-language answer
96
+ # into Shell. Returning None also avoids leaking the answer if a user
97
+ # calls request() directly in the interactive Python Shell.
98
+ sys.stdout.write(START + encoded + END + "\n")
99
+ sys.stdout.flush()
100
+ return None
101
+ except Exception:
102
+ print("[AI Request] Yêu cầu thất bại. Kiểm tra kết nối và api.txt.")
103
+ return None
104
+
105
+
106
+ def load_plugin(_backend=None):
107
+ builtins.request = request
@@ -0,0 +1,118 @@
1
+ """Frontend for Thonny AI Request.
2
+
3
+ Only reacts to Shell text insertion events that contain our private marker.
4
+ It deliberately does not poll the whole Shell in a recurring timer.
5
+ """
6
+ import base64
7
+ import itertools
8
+
9
+ from thonny import get_workbench
10
+
11
+ START = "__THONNY_AI_REQUEST_BEGIN__"
12
+ END = "__THONNY_AI_REQUEST_END__"
13
+ LINK_COLOR = "#F2F2F2"
14
+ _TAG_PREFIX = "thonny_ai_answer_"
15
+ _tag_counter = itertools.count(1)
16
+ _pending_widgets = set()
17
+
18
+
19
+ def _widget_exists(widget):
20
+ try:
21
+ return bool(widget.winfo_exists())
22
+ except Exception:
23
+ return False
24
+
25
+
26
+ def _copy_to_clipboard(widget, answer):
27
+ try:
28
+ widget.clipboard_clear()
29
+ widget.clipboard_append(answer)
30
+ widget.update_idletasks()
31
+ except Exception:
32
+ # Clipboard problems must not break the Shell.
33
+ pass
34
+ return "break"
35
+
36
+
37
+ def _replace_markers(widget):
38
+ """Replace complete answer markers using Thonny's direct text API.
39
+
40
+ Returns True only when a begin marker exists but its end marker has not
41
+ arrived yet, so the caller should retry briefly. No background polling.
42
+ """
43
+ if not _widget_exists(widget):
44
+ return False
45
+
46
+ try:
47
+ while True:
48
+ start_index = widget.search(START, "1.0", "end-1c")
49
+ if not start_index:
50
+ return False
51
+
52
+ payload_index = widget.index(f"{start_index} + {len(START)} chars")
53
+ end_index = widget.search(END, payload_index, "end-1c")
54
+ if not end_index:
55
+ return True
56
+
57
+ encoded = widget.get(payload_index, end_index)
58
+ after_end = widget.index(f"{end_index} + {len(END)} chars")
59
+ try:
60
+ answer = base64.b64decode(encoded.encode("ascii"), validate=True).decode("utf-8")
61
+ except Exception:
62
+ # Remove malformed internal marker rather than leaving noisy data.
63
+ widget.direct_delete(start_index, after_end)
64
+ continue
65
+
66
+ tag = _TAG_PREFIX + str(next(_tag_counter))
67
+ widget.tag_configure(tag, foreground=LINK_COLOR, underline=True)
68
+ widget.tag_raise(tag)
69
+ widget.tag_bind(tag, "<Enter>", lambda _event, w=widget: w.configure(cursor="hand2"))
70
+ widget.tag_bind(tag, "<Leave>", lambda _event, w=widget: w.configure(cursor=""))
71
+ widget.tag_bind(
72
+ tag,
73
+ "<Button-1>",
74
+ lambda _event, w=widget, a=answer: _copy_to_clipboard(w, a),
75
+ )
76
+
77
+ # The ordinary Tk Text delete/insert methods are blocked by
78
+ # Thonny's read-only Shell. direct_delete/direct_insert are the
79
+ # intended escape hatch for programmatic Shell rendering.
80
+ widget.direct_delete(start_index, after_end)
81
+ widget.direct_insert(start_index, "Click here", ("io", "stdout", tag))
82
+ except Exception:
83
+ # Keep the IDE usable even if a future Thonny version changes internals.
84
+ return False
85
+
86
+
87
+ def _process_widget(widget, attempt=0):
88
+ retry = _replace_markers(widget)
89
+ if retry and attempt < 20 and _widget_exists(widget):
90
+ try:
91
+ widget.after(50, lambda w=widget, n=attempt + 1: _process_widget(w, n))
92
+ return
93
+ except Exception:
94
+ pass
95
+ _pending_widgets.discard(widget)
96
+
97
+
98
+ def _on_text_insert(event):
99
+ inserted = getattr(event, "text", "")
100
+ if START not in inserted and END not in inserted:
101
+ return
102
+
103
+ widget = getattr(event, "text_widget", None)
104
+ if widget is None or widget in _pending_widgets:
105
+ return
106
+
107
+ _pending_widgets.add(widget)
108
+ try:
109
+ widget.after_idle(lambda w=widget: _process_widget(w))
110
+ except Exception:
111
+ _pending_widgets.discard(widget)
112
+
113
+
114
+ def load_plugin():
115
+ # Thonny emits TextInsert from its EnhancedText direct insertion method.
116
+ # The handler is filtered before doing any search, so normal Shell output
117
+ # incurs no repeated scans or timer callbacks.
118
+ get_workbench().bind("TextInsert", _on_text_insert, True)