moodlesolver 2.0.0__tar.gz → 2.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.
Files changed (24) hide show
  1. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/PKG-INFO +7 -2
  2. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/README.md +6 -1
  3. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/__init__.py +2 -1
  4. moodlesolver-2.1.0/moodlesolver/aipreview.py +26 -0
  5. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/jsonapi.py +17 -4
  6. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver.egg-info/PKG-INFO +7 -2
  7. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver.egg-info/SOURCES.txt +1 -0
  8. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/pyproject.toml +1 -1
  9. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/LICENSE +0 -0
  10. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/airunner.py +0 -0
  11. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/cdn.py +0 -0
  12. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/client.py +0 -0
  13. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/ctx.py +0 -0
  14. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/errors.py +0 -0
  15. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/mcp_server.py +0 -0
  16. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/parse.py +0 -0
  17. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/quizapi.py +0 -0
  18. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/schema/quiz_protocol.json +0 -0
  19. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver/vision.py +0 -0
  20. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver.egg-info/dependency_links.txt +0 -0
  21. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver.egg-info/entry_points.txt +0 -0
  22. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver.egg-info/requires.txt +0 -0
  23. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/moodlesolver.egg-info/top_level.txt +0 -0
  24. {moodlesolver-2.0.0 → moodlesolver-2.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moodlesolver
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Moodle quiz automation library and MCP server with local OCR
5
5
  Author: i-execute
6
6
  License: MIT
@@ -80,7 +80,12 @@ asyncio.run(main())
80
80
  Каждый ответ содержит: `page`, `question`, `question_hash`, `qtype`, `options`, `checked` (что реально отмечено на сервере), `selects`, `text_inputs`, `timeleft`, `buttons`, а также:
81
81
 
82
82
  - `question_image_b64` / `option_images_b64` — картинки в base64 (или ссылки при `image_mode="url"`)
83
- - `ocr` — локальное превью каждой картинки: `{"formula": "…", "ru": "…", "en": "…"}`
83
+ - `ocr` — локальное превью каждой картинки: `{"formula": "…", "ru": "…", "en": "…"}`, а при `ai_preview=True` ещё `ai_preview` — описание картинок vision-моделью OpenCode (mimo-v2.6-flash-free по умолчанию)
84
+
85
+ ```python
86
+ api = JsonAPI(q, ai_preview=True) # включить AI-описание картинок
87
+ api = JsonAPI(q, ai_preview=True, ai_model="opencode/longcat-2.5-preview-free")
88
+ ```
84
89
 
85
90
  Подробная схема: `moodlesolver/schema/quiz_protocol.json`.
86
91
 
@@ -63,7 +63,12 @@ asyncio.run(main())
63
63
  Каждый ответ содержит: `page`, `question`, `question_hash`, `qtype`, `options`, `checked` (что реально отмечено на сервере), `selects`, `text_inputs`, `timeleft`, `buttons`, а также:
64
64
 
65
65
  - `question_image_b64` / `option_images_b64` — картинки в base64 (или ссылки при `image_mode="url"`)
66
- - `ocr` — локальное превью каждой картинки: `{"formula": "…", "ru": "…", "en": "…"}`
66
+ - `ocr` — локальное превью каждой картинки: `{"formula": "…", "ru": "…", "en": "…"}`, а при `ai_preview=True` ещё `ai_preview` — описание картинок vision-моделью OpenCode (mimo-v2.6-flash-free по умолчанию)
67
+
68
+ ```python
69
+ api = JsonAPI(q, ai_preview=True) # включить AI-описание картинок
70
+ api = JsonAPI(q, ai_preview=True, ai_model="opencode/longcat-2.5-preview-free")
71
+ ```
67
72
 
68
73
  Подробная схема: `moodlesolver/schema/quiz_protocol.json`.
69
74
 
@@ -5,6 +5,7 @@ from .ctx import CtxAPI
5
5
  from .airunner import AIRunner
6
6
  from .jsonapi import JsonAPI
7
7
  from .vision import ocr_file, make_on_ocr
8
+ from .aipreview import ai_preview, opencode_available
8
9
  from .cdn import serve_dumps
9
10
  from .mcp_server import main as mcp_main
10
- __all__ = ['AsyncMoodle', 'AsyncTab', 'CamofoxError', 'parse_page', 'QuizAPI', 'CtxAPI', 'AIRunner', 'JsonAPI', 'mcp_main']
11
+ __all__ = ['AsyncMoodle', 'AsyncTab', 'CamofoxError', 'parse_page', 'QuizAPI', 'CtxAPI', 'AIRunner', 'JsonAPI', 'mcp_main', 'ai_preview', 'opencode_available', 'serve_dumps', 'ocr_file', 'make_on_ocr']
@@ -0,0 +1,26 @@
1
+ import shutil
2
+ import subprocess
3
+
4
+ OC_DEFAULT_MODEL = "opencode/mimo-v2.6-flash-free"
5
+
6
+
7
+ def opencode_available():
8
+ return shutil.which("opencode") is not None
9
+
10
+
11
+ def ai_preview(paths, prompt="Опиши что на картинке, весь текст перепиши",
12
+ model=OC_DEFAULT_MODEL, timeout=120):
13
+ if not opencode_available() or not paths:
14
+ return None
15
+ cmd = ["opencode", "run", prompt, "--model", model]
16
+ for p in paths:
17
+ cmd += ["-f", p]
18
+ try:
19
+ out = subprocess.run(cmd, capture_output=True, text=True,
20
+ timeout=timeout).stdout.strip()
21
+ except Exception:
22
+ return None
23
+ if not out or "Error" in out.splitlines()[0]:
24
+ return None
25
+ lines = [ln for ln in out.splitlines() if not ln.startswith(">")]
26
+ return "\n".join(lines).strip() or None
@@ -28,8 +28,9 @@ def _qtype(st):
28
28
  return 'unknown'
29
29
 
30
30
  class JsonAPI:
31
-
32
- def __init__(self, quiz_api, on_ocr=None, image_mode='b64', image_base_url=None):
31
+ def __init__(self, quiz_api, on_ocr=None, image_mode='b64',
32
+ image_base_url=None, ai_preview=False,
33
+ ai_model=None):
33
34
  self.q = quiz_api
34
35
  if on_ocr is None:
35
36
  from .vision import make_on_ocr
@@ -37,6 +38,9 @@ class JsonAPI:
37
38
  self.on_ocr = on_ocr
38
39
  self.image_mode = image_mode
39
40
  self.image_base_url = image_base_url
41
+ self.ai = bool(ai_preview)
42
+ from .aipreview import OC_DEFAULT_MODEL
43
+ self.ai_model = ai_model or OC_DEFAULT_MODEL
40
44
 
41
45
  async def handle(self, command):
42
46
  c = json.loads(command) if isinstance(command, str) else command
@@ -78,8 +82,6 @@ class JsonAPI:
78
82
  return v
79
83
 
80
84
  async def _ocr_preview(self, st):
81
- if not self.on_ocr:
82
- return None
83
85
  import asyncio as aio
84
86
  files = []
85
87
  if st.get('question_image_file'):
@@ -88,6 +90,7 @@ class JsonAPI:
88
90
  files.append((f, f))
89
91
  if not files:
90
92
  return None
93
+ from .vision import _resolve
91
94
 
92
95
  async def one(name, f):
93
96
  ru, en, formula = await aio.gather(aio.to_thread(self.on_ocr, f, 'текст русский'), aio.to_thread(self.on_ocr, f, 'text english'), aio.to_thread(self.on_ocr, f, 'формула'), return_exceptions=True)
@@ -102,6 +105,16 @@ class JsonAPI:
102
105
  out.setdefault('errors', []).append(str(p)[:60])
103
106
  else:
104
107
  out[p[0]] = p[1]
108
+ if self.ai:
109
+ try:
110
+ from .aipreview import ai_preview
111
+ paths = [_resolve(f) for _, f in files]
112
+ text = await aio.to_thread(ai_preview, paths,
113
+ model=self.ai_model)
114
+ if text:
115
+ out['ai_preview'] = text[:2000]
116
+ except Exception:
117
+ pass
105
118
  return out or None
106
119
 
107
120
  async def _select(self, c):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moodlesolver
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Moodle quiz automation library and MCP server with local OCR
5
5
  Author: i-execute
6
6
  License: MIT
@@ -80,7 +80,12 @@ asyncio.run(main())
80
80
  Каждый ответ содержит: `page`, `question`, `question_hash`, `qtype`, `options`, `checked` (что реально отмечено на сервере), `selects`, `text_inputs`, `timeleft`, `buttons`, а также:
81
81
 
82
82
  - `question_image_b64` / `option_images_b64` — картинки в base64 (или ссылки при `image_mode="url"`)
83
- - `ocr` — локальное превью каждой картинки: `{"formula": "…", "ru": "…", "en": "…"}`
83
+ - `ocr` — локальное превью каждой картинки: `{"formula": "…", "ru": "…", "en": "…"}`, а при `ai_preview=True` ещё `ai_preview` — описание картинок vision-моделью OpenCode (mimo-v2.6-flash-free по умолчанию)
84
+
85
+ ```python
86
+ api = JsonAPI(q, ai_preview=True) # включить AI-описание картинок
87
+ api = JsonAPI(q, ai_preview=True, ai_model="opencode/longcat-2.5-preview-free")
88
+ ```
84
89
 
85
90
  Подробная схема: `moodlesolver/schema/quiz_protocol.json`.
86
91
 
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  moodlesolver/__init__.py
5
+ moodlesolver/aipreview.py
5
6
  moodlesolver/airunner.py
6
7
  moodlesolver/cdn.py
7
8
  moodlesolver/client.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "moodlesolver"
7
- version = "2.0.0"
7
+ version = "2.1.0"
8
8
  description = "Moodle quiz automation library and MCP server with local OCR"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes