AtCoderStudyBooster 0.4.1__tar.gz → 0.4.2__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 (40) hide show
  1. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/PKG-INFO +2 -1
  2. atcoderstudybooster-0.4.2/atcdr/ai.py +434 -0
  3. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/cli.py +2 -2
  4. atcoderstudybooster-0.4.2/atcdr/util/openai.py +45 -0
  5. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/parse.py +1 -1
  6. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/pyproject.toml +2 -1
  7. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/requirements-dev.lock +37 -0
  8. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/requirements.lock +37 -0
  9. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/uv.lock +604 -1
  10. atcoderstudybooster-0.4.1/atcdr/generate.py +0 -197
  11. atcoderstudybooster-0.4.1/atcdr/util/gpt.py +0 -116
  12. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.github/workflows/deploy.yaml +0 -0
  13. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.gitignore +0 -0
  14. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo0-ja.gif +0 -0
  15. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo0.gif +0 -0
  16. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo1.png +0 -0
  17. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo2.png +0 -0
  18. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo3.png +0 -0
  19. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo4.png +0 -0
  20. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.pre-commit-config.yaml +0 -0
  21. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.python-version +0 -0
  22. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.vscode/extensions.json +0 -0
  23. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.vscode/setting.json +0 -0
  24. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.vscode/tasks.json +0 -0
  25. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/README.ja.md +0 -0
  26. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/README.md +0 -0
  27. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/__init__.py +0 -0
  28. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/download.py +0 -0
  29. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/login.py +0 -0
  30. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/logout.py +0 -0
  31. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/markdown.py +0 -0
  32. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/open.py +0 -0
  33. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/submit.py +0 -0
  34. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/test.py +0 -0
  35. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/__init__.py +0 -0
  36. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/fileops.py +0 -0
  37. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/filetype.py +0 -0
  38. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/i18n.py +0 -0
  39. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/problem.py +0 -0
  40. {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: AtCoderStudyBooster
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: A tool to download and manage AtCoder problems.
5
5
  Project-URL: Homepage, https://github.com/yuta6/AtCoderStudyBooster
6
6
  Author-email: yuta6 <46110512+yuta6@users.noreply.github.com>
@@ -9,6 +9,7 @@ Requires-Python: >=3.8
9
9
  Requires-Dist: beautifulsoup4
10
10
  Requires-Dist: click-aliases>=1.0.5
11
11
  Requires-Dist: markdownify==0.13.1
12
+ Requires-Dist: openai>=1.99.6
12
13
  Requires-Dist: pywebview>=5.4
13
14
  Requires-Dist: questionary>=2.0.1
14
15
  Requires-Dist: requests
@@ -0,0 +1,434 @@
1
+ import json
2
+ import random
3
+ import time
4
+ from pathlib import Path
5
+ from typing import Any, Callable, Dict, List, Optional
6
+
7
+ import rich_click as click
8
+ from openai import BadRequestError, OpenAI
9
+ from rich.console import Console
10
+ from rich.live import Live
11
+ from rich.markup import escape
12
+ from rich.panel import Panel
13
+ from rich.syntax import Syntax
14
+ from rich.table import Table
15
+ from rich.text import Text
16
+
17
+ from atcdr.test import (
18
+ LabeledTestCase,
19
+ ResultStatus,
20
+ TestCase,
21
+ TestRunner,
22
+ )
23
+ from atcdr.util.fileops import add_file_selector
24
+ from atcdr.util.filetype import (
25
+ COMPILED_LANGUAGES,
26
+ FILE_EXTENSIONS,
27
+ INTERPRETED_LANGUAGES,
28
+ Lang,
29
+ str2lang,
30
+ )
31
+ from atcdr.util.i18n import _
32
+ from atcdr.util.openai import set_api_key
33
+ from atcdr.util.parse import ProblemHTML
34
+
35
+
36
+ def render_result_for_GPT(test: TestRunner) -> tuple[str, bool]:
37
+ results = list(test)
38
+ match test.info.summary:
39
+ case ResultStatus.CE:
40
+ return f'Compile Error \n {test.info.compiler_message}', False
41
+ case _:
42
+ message_for_gpt = ''.join(
43
+ (
44
+ f'\n{r.label} => {r.result.passed.value}, Execution Time : {r.result.executed_time}\n'
45
+ f'\nInput :\n{r.testcase.input}\nOutput :\n{r.result.output}\nExpected :\n{r.testcase.output}\n'
46
+ if r.result.passed == ResultStatus.WA
47
+ else f'\n{r.label} => {r.result.passed.value}\nInput :\n{r.testcase.input}\nOutput :\n{r.result.output}\n'
48
+ )
49
+ for r in results
50
+ )
51
+ return message_for_gpt, False
52
+
53
+
54
+ def display_test_results(console: Console, test: TestRunner) -> None:
55
+ results = list(test)
56
+
57
+ table = Table(title='🧪 Test Results')
58
+ table.add_column('Test Case', style='cyan', no_wrap=True)
59
+ table.add_column('Status', justify='center', no_wrap=True)
60
+ table.add_column('Input', style='dim', max_width=30)
61
+ table.add_column('Output', style='yellow', max_width=30)
62
+ table.add_column('Expected', style='green', max_width=30)
63
+
64
+ for r in results:
65
+ if r.result.passed == ResultStatus.AC:
66
+ status = '[green]✅ AC[/green]'
67
+ elif r.result.passed == ResultStatus.WA:
68
+ status = '[red]❌ WA[/red]'
69
+ elif r.result.passed == ResultStatus.TLE:
70
+ status = '[yellow]⏰ TLE[/yellow]'
71
+ elif r.result.passed == ResultStatus.RE:
72
+ status = '[red]💥 RE[/red]'
73
+ else:
74
+ status = f'[red]{r.result.passed.value}[/red]'
75
+
76
+ input_preview = escape(
77
+ r.testcase.input.strip()[:50] + '...'
78
+ if len(r.testcase.input.strip()) > 50
79
+ else r.testcase.input.strip()
80
+ )
81
+ output_preview = escape(
82
+ r.result.output.strip()[:50] + '...'
83
+ if len(r.result.output.strip()) > 50
84
+ else r.result.output.strip()
85
+ )
86
+ expected_preview = escape(
87
+ r.testcase.output.strip()[:50] + '...'
88
+ if len(r.testcase.output.strip()) > 50
89
+ else r.testcase.output.strip()
90
+ )
91
+
92
+ table.add_row(r.label, status, input_preview, output_preview, expected_preview)
93
+
94
+ console.print(table)
95
+
96
+
97
+ def create_func(labeled_cases: list[LabeledTestCase], model: str):
98
+ def test_example_case(code: str, language: str) -> str:
99
+ language_enum: Lang = str2lang(language)
100
+ source_path = Path(f'{model}{FILE_EXTENSIONS[language_enum]}')
101
+ source_path.write_text(code, encoding='utf-8')
102
+ test = TestRunner(str(source_path), labeled_cases)
103
+ message_for_gpt, _ = render_result_for_GPT(test)
104
+ return message_for_gpt
105
+
106
+ def execute_code(input: Optional[str], code: str, language: str) -> str:
107
+ language_enum: Lang = str2lang(language)
108
+ random_name = random.randint(0, 100_000_000)
109
+ source_path = Path(f'tmp{random_name}{FILE_EXTENSIONS[language_enum]}')
110
+ source_path.write_text(code, encoding='utf-8')
111
+ labeled_cases = [LabeledTestCase('case by gpt', TestCase(input or '', ''))]
112
+ test = TestRunner(str(source_path), labeled_cases)
113
+ labeled_result = next(test)
114
+ source_path.unlink(missing_ok=True)
115
+ return labeled_result.result.output
116
+
117
+ return test_example_case, execute_code
118
+
119
+
120
+ def solve_problem(path: Path, lang: Lang, model: str) -> None:
121
+ console = Console()
122
+ content = path.read_text(encoding='utf-8')
123
+ html = ProblemHTML(content)
124
+ md = html.make_problem_markdown('en')
125
+ labeled_cases = html.load_labeled_testcase()
126
+
127
+ test_example_case, execute_code = create_func(labeled_cases, model)
128
+
129
+ # Responses API 形式のツール定義(トップレベル)
130
+ TOOLS = [
131
+ {
132
+ 'type': 'function',
133
+ 'name': 'test_example_case',
134
+ 'description': 'Run the given source code against example test cases and return a summarized result.',
135
+ 'parameters': {
136
+ 'type': 'object',
137
+ 'properties': {
138
+ 'code': {'type': 'string'},
139
+ 'language': {
140
+ 'type': 'string',
141
+ 'enum': [
142
+ lang.value
143
+ for lang in (COMPILED_LANGUAGES + INTERPRETED_LANGUAGES)
144
+ ],
145
+ },
146
+ },
147
+ 'required': ['code', 'language'],
148
+ 'additionalProperties': False,
149
+ },
150
+ 'strict': True,
151
+ },
152
+ {
153
+ 'type': 'function',
154
+ 'name': 'execute_code',
155
+ 'description': 'Execute the given source code with a single input and return the actual output.',
156
+ 'parameters': {
157
+ 'type': 'object',
158
+ 'properties': {
159
+ 'input': {'type': 'string'},
160
+ 'code': {'type': 'string'},
161
+ 'language': {
162
+ 'type': 'string',
163
+ 'enum': [
164
+ lang.value
165
+ for lang in (COMPILED_LANGUAGES + INTERPRETED_LANGUAGES)
166
+ ],
167
+ },
168
+ },
169
+ 'required': ['input', 'code', 'language'],
170
+ 'additionalProperties': False,
171
+ },
172
+ 'strict': True,
173
+ },
174
+ ]
175
+
176
+ client = OpenAI()
177
+ if set_api_key() is None:
178
+ console.print('[red]OpenAI API key is not set.[/red]')
179
+ return
180
+
181
+ system_prompt = f"""You are a competitive programming assistant for {lang.value}.
182
+ The user will provide problems in Markdown format.
183
+ Read the problem carefully and output a complete, correct, and efficient solution in {lang.value}.
184
+ Use standard input and output. Do not omit any code.
185
+ Always pay close attention to algorithmic complexity (time and space).
186
+ Choose the most optimal algorithms and data structures so that the solution runs within time limits even for the largest possible inputs.
187
+
188
+ Use the provided tool test_example_case to run the example test cases from the problem statement.
189
+ If tests do not pass, fix the code and repeat.
190
+ The last tested code will be automatically saved to a local file on the user's computer.
191
+ You do not need to include the final source code in your response.
192
+ Simply confirm to the user that all tests passed, or briefly explain if they did not.
193
+ Once you run test_example_case, the exact code you tested will already be saved locally on the user's machine, so sending it again in the response is unnecessary."""
194
+
195
+ # ツール名→ローカル実装のディスパッチ
196
+ tool_impl: Dict[str, Callable[..., Any]] = {
197
+ 'test_example_case': test_example_case,
198
+ 'execute_code': lambda **p: execute_code(
199
+ p.get('input', ''), # ← 空なら空文字に
200
+ p.get('code', ''),
201
+ p.get('language', lang.value),
202
+ ),
203
+ }
204
+
205
+ console.print(f'Solving :{path} Language: {lang.value} / Model: {model}')
206
+
207
+ context_msgs = [
208
+ {'role': 'system', 'content': system_prompt},
209
+ {'role': 'user', 'content': md},
210
+ ]
211
+ turn = 1
212
+ assistant_text = Text()
213
+
214
+ def call_model():
215
+ try:
216
+ return client.responses.create(
217
+ model=model,
218
+ input=context_msgs,
219
+ tools=TOOLS,
220
+ tool_choice='auto',
221
+ include=['reasoning.encrypted_content'],
222
+ store=False,
223
+ )
224
+ except BadRequestError as e:
225
+ body = getattr(getattr(e, 'response', None), 'json', lambda: None)()
226
+ console.print(
227
+ Panel.fit(f'{e}\n\n{body}', title='API Error', border_style='red')
228
+ )
229
+ raise
230
+
231
+ while True:
232
+ start_time = time.time()
233
+ with Live(
234
+ Panel(
235
+ f'[bold blue]🤔 Thinking... (turn {turn})[/bold blue]\n[dim]Elapsed: 0.0s[/dim]',
236
+ border_style='blue',
237
+ ),
238
+ console=console,
239
+ refresh_per_second=10,
240
+ ) as live:
241
+
242
+ def update_timer():
243
+ elapsed = time.time() - start_time
244
+ live.update(
245
+ Panel(
246
+ f'[bold blue]🤔 Thinking... (turn {turn})[/bold blue]\n[dim]Elapsed: {elapsed:.1f}s[/dim]',
247
+ border_style='blue',
248
+ )
249
+ )
250
+
251
+ import threading
252
+
253
+ resp = None
254
+ error = None
255
+
256
+ def model_call():
257
+ nonlocal resp, error
258
+ try:
259
+ resp = call_model()
260
+ except Exception as e:
261
+ error = e
262
+
263
+ thread = threading.Thread(target=model_call)
264
+ thread.start()
265
+
266
+ while thread.is_alive():
267
+ update_timer()
268
+ time.sleep(0.1)
269
+
270
+ thread.join()
271
+
272
+ if error:
273
+ raise error
274
+
275
+ elapsed = time.time() - start_time
276
+ live.update(
277
+ Panel(
278
+ f'[bold green]✓ Completed thinking (turn {turn})[/bold green]\n[dim]Time taken: {elapsed:.1f}s[/dim]',
279
+ border_style='green',
280
+ )
281
+ )
282
+
283
+ # Display token usage
284
+ if resp and hasattr(resp, 'usage') and resp.usage:
285
+ usage = resp.usage
286
+ input_tokens = getattr(usage, 'input_tokens', 0)
287
+ output_tokens = getattr(usage, 'output_tokens', 0)
288
+ total_tokens = getattr(usage, 'total_tokens', 0)
289
+
290
+ # Check for cached tokens
291
+ cached_tokens = 0
292
+ if hasattr(usage, 'input_tokens_details'):
293
+ details = usage.input_tokens_details
294
+ if hasattr(details, 'cached_tokens'):
295
+ cached_tokens = details.cached_tokens
296
+
297
+ # Check for reasoning tokens
298
+ reasoning_tokens = 0
299
+ if hasattr(usage, 'output_tokens_details'):
300
+ details = usage.output_tokens_details
301
+ if hasattr(details, 'reasoning_tokens'):
302
+ reasoning_tokens = details.reasoning_tokens
303
+
304
+ token_msg = f'[dim]Tokens - Input: {input_tokens:,}'
305
+ if cached_tokens > 0:
306
+ token_msg += f' (cached: {cached_tokens:,})'
307
+ token_msg += f' | Output: {output_tokens:,}'
308
+ if reasoning_tokens > 0:
309
+ token_msg += f' (reasoning: {reasoning_tokens:,})'
310
+ token_msg += f' | Total: {total_tokens:,}[/dim]'
311
+ console.print(token_msg)
312
+
313
+ if resp and getattr(resp, 'output_text', None):
314
+ assistant_text.append(resp.output_text)
315
+
316
+ output_content = str(resp.output_text).strip()
317
+ if any(
318
+ keyword in output_content
319
+ for keyword in [
320
+ 'def ',
321
+ 'class ',
322
+ 'import ',
323
+ 'from ',
324
+ '#include',
325
+ 'public class',
326
+ ]
327
+ ):
328
+ try:
329
+ syntax = Syntax(
330
+ output_content, lang, theme='monokai', line_numbers=True
331
+ )
332
+ console.print(
333
+ Panel(
334
+ syntax,
335
+ title='Assistant Output (Code)',
336
+ border_style='green',
337
+ )
338
+ )
339
+ except Exception:
340
+ console.print(
341
+ Panel(
342
+ assistant_text,
343
+ title='Assistant Output',
344
+ border_style='green',
345
+ )
346
+ )
347
+ else:
348
+ console.print(
349
+ Panel(
350
+ assistant_text, title='Assistant Output', border_style='green'
351
+ )
352
+ )
353
+
354
+ if resp and hasattr(resp, 'output'):
355
+ context_msgs += resp.output
356
+
357
+ # function_call を収集
358
+ calls: List[dict] = []
359
+ for o in resp.output:
360
+ if getattr(o, 'type', '') == 'function_call':
361
+ try:
362
+ args = json.loads(o.arguments or '{}')
363
+ except Exception:
364
+ args = {}
365
+ call_id = getattr(o, 'call_id', None) or getattr(
366
+ o, 'id'
367
+ ) # ★ ここがポイント
368
+ calls.append({'name': o.name, 'call_id': call_id, 'args': args})
369
+ else:
370
+ calls = []
371
+
372
+ if not calls:
373
+ console.print(
374
+ Panel.fit('✅ Done (no more tool calls).', border_style='green')
375
+ )
376
+ break
377
+
378
+ # ツールを実行し、function_call_output を context に積む
379
+ for c in calls:
380
+ args_str = json.dumps(c['args'], ensure_ascii=False) if c['args'] else ''
381
+ console.print(
382
+ Panel.fit(
383
+ f"Tool: [bold]{c['name']}[/bold]\nargs: {args_str}",
384
+ title=f"function_call ({c['call_id']})",
385
+ border_style='cyan',
386
+ )
387
+ )
388
+
389
+ impl = tool_impl.get(c['name'])
390
+ if not impl:
391
+ out = f"[ERROR] Unknown tool: {c['name']}"
392
+ else:
393
+ try:
394
+ with console.status(f"Running {c['name']}...", spinner='dots'):
395
+ out = impl(**c['args']) if c['args'] else impl()
396
+ except TypeError:
397
+ out = impl(
398
+ **{
399
+ k: v
400
+ for k, v in c['args'].items()
401
+ if k in impl.__code__.co_varnames
402
+ }
403
+ )
404
+ except Exception as e:
405
+ out = f"[Tool '{c['name']}' error] {e}"
406
+
407
+ console.print(
408
+ Panel(
409
+ str(out) or '(no output)',
410
+ title=f"{c['name']} result",
411
+ border_style='magenta',
412
+ )
413
+ )
414
+
415
+ context_msgs.append(
416
+ {
417
+ 'type': 'function_call_output',
418
+ 'call_id': c['call_id'],
419
+ 'output': str(out),
420
+ }
421
+ )
422
+
423
+ turn += 1
424
+
425
+
426
+ @click.command(short_help=_('cmd_generate'), help=_('cmd_generate'))
427
+ @add_file_selector('files', filetypes=[Lang.HTML])
428
+ @click.option('--lang', default='Python', help=_('opt_output_lang'))
429
+ @click.option('--model', default='gpt-5-mini', help=_('opt_model'))
430
+ def ai(files, lang, model):
431
+ """HTMLファイルからコード生成またはテンプレート出力を行います。"""
432
+ lang_enum: Lang = str2lang(lang)
433
+ for path in files:
434
+ solve_problem(Path(path), lang_enum, model)
@@ -8,8 +8,8 @@ from rich.table import Table
8
8
  from rich.traceback import install
9
9
  from rich_click import RichGroup
10
10
 
11
+ from atcdr.ai import ai
11
12
  from atcdr.download import download
12
- from atcdr.generate import generate
13
13
  from atcdr.login import login
14
14
  from atcdr.logout import logout
15
15
  from atcdr.markdown import markdown
@@ -76,7 +76,7 @@ def cli():
76
76
  cli.add_command(test, aliases=['t'])
77
77
  cli.add_command(download, aliases=['d'])
78
78
  cli.add_command(open_files, 'open', aliases=['o'])
79
- cli.add_command(generate, aliases=['g'])
79
+ cli.add_command(ai)
80
80
  cli.add_command(markdown, aliases=['md'])
81
81
  cli.add_command(submit, aliases=['s'])
82
82
  cli.add_command(login)
@@ -0,0 +1,45 @@
1
+ import os
2
+ from typing import Optional
3
+
4
+ import requests
5
+
6
+ from atcdr.util.i18n import _
7
+
8
+
9
+ def set_api_key() -> Optional[str]:
10
+ api_key = os.getenv('OPENAI_API_KEY')
11
+ if api_key and validate_api_key(api_key):
12
+ return api_key
13
+ elif api_key:
14
+ print(_('api_key_validation_failed'))
15
+ else:
16
+ pass
17
+
18
+ api_key = input(_('get_api_key_prompt'))
19
+ if validate_api_key(api_key):
20
+ print(_('api_key_test_success'))
21
+ print(_('save_api_key_prompt'))
22
+ if input() == 'y':
23
+ zshrc_path = os.path.expanduser('~/.zshrc')
24
+ with open(zshrc_path, 'a') as f:
25
+ f.write(f'export OPENAI_API_KEY={api_key}\n')
26
+ print(_('api_key_saved', zshrc_path))
27
+ os.environ['OPENAI_API_KEY'] = api_key
28
+ return api_key
29
+ else:
30
+ print(_('api_key_required'))
31
+ return None
32
+
33
+
34
+ def validate_api_key(api_key: str) -> bool:
35
+ headers = {
36
+ 'Content-Type': 'application/json',
37
+ 'Authorization': f'Bearer {api_key}',
38
+ }
39
+
40
+ response = requests.get('https://api.openai.com/v1/models', headers=headers)
41
+ if response.status_code == 200:
42
+ return True
43
+ else:
44
+ print(_('api_key_validation_error'))
45
+ return False
@@ -110,7 +110,7 @@ class ProblemHTML(HTML):
110
110
  def load_labeled_testcase(self) -> List:
111
111
  from atcdr.test import LabeledTestCase, TestCase
112
112
 
113
- problem_part = self.abstract_problem_part(i18n.language)
113
+ problem_part = self.abstract_problem_part('en')
114
114
  if problem_part is None:
115
115
  return []
116
116
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "AtCoderStudyBooster"
3
- version = "0.4.1"
3
+ version = "0.4.2"
4
4
  description = "A tool to download and manage AtCoder problems."
5
5
  authors = [
6
6
  { name = "yuta6", email = "46110512+yuta6@users.noreply.github.com" }
@@ -17,6 +17,7 @@ dependencies = [
17
17
  "pywebview>=5.4",
18
18
  "rich-click>=1.8.8",
19
19
  "click-aliases>=1.0.5",
20
+ "openai>=1.99.6",
20
21
  ]
21
22
  readme = "README.md"
22
23
  requires-python = ">= 3.8"
@@ -10,12 +10,19 @@
10
10
  # universal: false
11
11
 
12
12
  -e file:.
13
+ annotated-types==0.7.0
14
+ # via pydantic
15
+ anyio==4.10.0
16
+ # via httpx
17
+ # via openai
13
18
  beautifulsoup4==4.12.3
14
19
  # via atcoderstudybooster
15
20
  # via markdownify
16
21
  bottle==0.13.2
17
22
  # via pywebview
18
23
  certifi==2024.7.4
24
+ # via httpcore
25
+ # via httpx
19
26
  # via requests
20
27
  cfgv==3.4.0
21
28
  # via pre-commit
@@ -28,14 +35,26 @@ click-aliases==1.0.5
28
35
  # via atcoderstudybooster
29
36
  distlib==0.3.8
30
37
  # via virtualenv
38
+ distro==1.9.0
39
+ # via openai
31
40
  filelock==3.15.4
32
41
  # via virtualenv
42
+ h11==0.16.0
43
+ # via httpcore
44
+ httpcore==1.0.9
45
+ # via httpx
46
+ httpx==0.28.1
47
+ # via openai
33
48
  identify==2.6.0
34
49
  # via pre-commit
35
50
  idna==3.7
51
+ # via anyio
52
+ # via httpx
36
53
  # via requests
37
54
  iniconfig==2.0.0
38
55
  # via pytest
56
+ jiter==0.10.0
57
+ # via openai
39
58
  markdown-it-py==3.0.0
40
59
  # via rich
41
60
  markdownify==0.13.1
@@ -47,6 +66,8 @@ mypy-extensions==1.0.0
47
66
  # via mypy
48
67
  nodeenv==1.9.1
49
68
  # via pre-commit
69
+ openai==1.99.9
70
+ # via atcoderstudybooster
50
71
  packaging==24.1
51
72
  # via pytest
52
73
  platformdirs==4.2.2
@@ -58,6 +79,10 @@ prompt-toolkit==3.0.36
58
79
  # via questionary
59
80
  proxy-tools==0.1.0
60
81
  # via pywebview
82
+ pydantic==2.11.7
83
+ # via openai
84
+ pydantic-core==2.33.2
85
+ # via pydantic
61
86
  pygments==2.18.0
62
87
  # via rich
63
88
  pytest==8.3.2
@@ -79,10 +104,15 @@ rich-click==1.8.8
79
104
  # via atcoderstudybooster
80
105
  six==1.16.0
81
106
  # via markdownify
107
+ sniffio==1.3.1
108
+ # via anyio
109
+ # via openai
82
110
  soupsieve==2.5
83
111
  # via beautifulsoup4
84
112
  tiktoken==0.7.0
85
113
  # via atcoderstudybooster
114
+ tqdm==4.67.1
115
+ # via openai
86
116
  types-beautifulsoup4==4.12.0.20240511
87
117
  # via atcoderstudybooster
88
118
  types-html5lib==1.1.11.20240806
@@ -90,9 +120,16 @@ types-html5lib==1.1.11.20240806
90
120
  types-requests==2.32.0.20240712
91
121
  # via atcoderstudybooster
92
122
  typing-extensions==4.12.2
123
+ # via anyio
93
124
  # via mypy
125
+ # via openai
126
+ # via pydantic
127
+ # via pydantic-core
94
128
  # via pywebview
95
129
  # via rich-click
130
+ # via typing-inspection
131
+ typing-inspection==0.4.1
132
+ # via pydantic
96
133
  urllib3==2.2.2
97
134
  # via requests
98
135
  # via types-requests