mirror of
https://github.com/codeflash-ai/codeflash-internal.git
synced 2026-05-04 18:25:18 +00:00
Merge branch 'main' into opt-impact-aseem
This commit is contained in:
commit
1cc26bf5b4
82 changed files with 10623 additions and 2760 deletions
6
.github/workflows/codeflash-aiservice.yaml
vendored
6
.github/workflows/codeflash-aiservice.yaml
vendored
|
|
@ -32,10 +32,12 @@ jobs:
|
|||
|
||||
- name: Install Project Dependencies
|
||||
run: |
|
||||
uv sync
|
||||
uv sync --refresh --isolated --active
|
||||
uv pip uninstall codeflash
|
||||
uv pip install pytest-asyncio
|
||||
uv pip install git+https://github.com/codeflash-ai/codeflash@main
|
||||
|
||||
- name: Run Codeflash to optimize code
|
||||
id: optimize_code
|
||||
run: |
|
||||
uv run codeflash
|
||||
uv run --active codeflash --async
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
optimizer/tests/__init__.py
|
||||
tests/optimizer/__init__.py
|
||||
optimizer/__init__.py
|
||||
optimizer/code_utils/postprocess_constants.py
|
||||
optimizer/code_utils/__init__.py
|
||||
authapp/__init__.py
|
||||
authapp/tests.py
|
||||
testgen/models.py
|
||||
testgen/tests/test_testgen.py
|
||||
testgen/tests/__init__.py
|
||||
tests/testgen/test_testgen.py
|
||||
tests/testgen/__init__.py
|
||||
testgen/__init__.py
|
||||
testgen/instrumentation/tests/__init__.py
|
||||
tests/testgen_instrumentation/__init__.py
|
||||
testgen/instrumentation/__init__.py
|
||||
testgen/postprocessing/tests/__init__.py
|
||||
testgen/postprocessing/tests/test_remove_asserts.py
|
||||
testgen/postprocessing/tests/test_validate_code.py
|
||||
tests/testgen_postprocessing/__init__.py
|
||||
tests/testgen_postprocessing/test_remove_asserts.py
|
||||
tests/testgen_postprocessing/test_validate_code.py
|
||||
testgen/postprocessing/__init__.py
|
||||
testgen/postprocessing/code_validator.py
|
||||
testgen/postprocessing/topdef_terminator.py
|
||||
testgen/postprocessing/removeassert_transformer.py
|
||||
log_features/__init__.py
|
||||
aiservice/middleware/__init__.py
|
||||
aiservice/tests/__init__.py
|
||||
tests/aiservice/__init__.py
|
||||
aiservice/management/__init__.py
|
||||
aiservice/management/commands/__init__.py
|
||||
aiservice/__init__.py
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import ast
|
||||
from collections import deque
|
||||
|
||||
|
||||
def find_init(node) -> bool:
|
||||
|
|
@ -11,9 +12,10 @@ def find_init(node) -> bool:
|
|||
bool: True if an __init__ function is found, False otherwise
|
||||
|
||||
"""
|
||||
# Check if current node is a function definition named '__init__'
|
||||
if isinstance(node, ast.FunctionDef) and node.name == "__init__":
|
||||
return True
|
||||
|
||||
# Recursively check all child nodes
|
||||
return any(find_init(child) for child in ast.iter_child_nodes(node))
|
||||
stack = deque([node])
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
if isinstance(current, ast.FunctionDef) and current.name == "__init__":
|
||||
return True
|
||||
stack.extend(ast.iter_child_nodes(current))
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -224,6 +224,6 @@ skip-magic-trailing-comma = true
|
|||
|
||||
[tool.codeflash]
|
||||
module-root = "."
|
||||
tests-root = "."
|
||||
tests-root = "tests"
|
||||
test-framework = "pytest"
|
||||
ignore-paths = []
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
from libcst import (
|
||||
Add,
|
||||
Arg,
|
||||
|
|
@ -17,6 +16,7 @@ from libcst import (
|
|||
Subtract,
|
||||
UnaryOperation,
|
||||
)
|
||||
from math import prod
|
||||
|
||||
|
||||
class TensorLimit(CSTTransformer):
|
||||
|
|
@ -74,10 +74,7 @@ class TensorLimit(CSTTransformer):
|
|||
|
||||
def _calculate_tensor_size(self, shape: list[int]) -> int:
|
||||
"""Calculates the total number of elements in a tensor."""
|
||||
total_size = 1 # Start with 1, not 0
|
||||
for s in shape:
|
||||
total_size *= s
|
||||
return total_size
|
||||
return prod(shape)
|
||||
|
||||
def _should_modify_tensor(self, shape: list[int]) -> tuple[bool, list[int]]:
|
||||
"""Determines if a tensor should be modified based on its size and the threshold."""
|
||||
|
|
@ -112,7 +109,9 @@ class TensorLimit(CSTTransformer):
|
|||
|
||||
if should_modify:
|
||||
# Create new arguments with the modified shape values
|
||||
new_args = [Arg(value=self._make_number_node(dim)) for dim in new_shape]
|
||||
new_args = [
|
||||
Arg(value=self._make_number_node(dim)) for dim in new_shape
|
||||
]
|
||||
# Return a modified call with the new arguments
|
||||
return updated_node.with_changes(args=new_args)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
revision = 3
|
||||
requires-python = ">=3.12.1, <4"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.13'",
|
||||
|
|
@ -90,25 +90,25 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.10.0"
|
||||
version = "4.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.9.1"
|
||||
version = "3.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142", size = 36870, upload-time = "2025-07-08T09:07:43.344Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7f/bf/0f3ecda32f1cb3bf1dca480aca08a7a8a3bdc4bed2343a103f30731565c9/asgiref-3.9.2.tar.gz", hash = "sha256:a0249afacb66688ef258ffe503528360443e2b9a8d8c4581b6ebefa58c841ef1", size = 36894, upload-time = "2025-09-23T15:00:55.136Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/3c/0464dcada90d5da0e71018c04a140ad6349558afb30b3051b4264cc5b965/asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c", size = 23790, upload-time = "2025-07-08T09:07:41.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/d1/69d02ce34caddb0a7ae088b84c356a625a93cd4ff57b2f97644c03fad905/asgiref-3.9.2-py3-none-any.whl", hash = "sha256:0b61526596219d70396548fc003635056856dba5d0d086f86476f10b33c75960", size = 23788, upload-time = "2025-09-23T15:00:53.627Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -164,49 +164,56 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.2"
|
||||
version = "3.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.2.1"
|
||||
version = "8.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -251,16 +258,16 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.2.5"
|
||||
version = "5.2.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "sqlparse" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/62/9b/779f853c3d2d58b9e08346061ff3e331cdec3fe3f53aae509e256412a593/django-5.2.5.tar.gz", hash = "sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae", size = 10859748, upload-time = "2025-08-06T08:26:29.978Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/96/bd84e2bb997994de8bcda47ae4560991084e86536541d7214393880f01a8/django-5.2.7.tar.gz", hash = "sha256:e0f6f12e2551b1716a95a63a1366ca91bbcd7be059862c1b18f989b1da356cdd", size = 10865812, upload-time = "2025-10-01T14:22:12.081Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/6e/98a1d23648e0085bb5825326af17612ecd8fc76be0ce96ea4dc35e17b926/django-5.2.5-py3-none-any.whl", hash = "sha256:2b2ada0ee8a5ff743a40e2b9820d1f8e24c11bac9ae6469cd548f0057ea6ddcd", size = 8302999, upload-time = "2025-08-06T08:26:23.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/ef/81f3372b5dd35d8d354321155d1a38894b2b766f576d0abffac4d8ae78d9/django-5.2.7-py3-none-any.whl", hash = "sha256:59a13a6515f787dec9d97a0438cd2efac78c8aca1c80025244b0fe507fe0754b", size = 8307145, upload-time = "2025-10-01T14:22:49.476Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -278,11 +285,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "executing"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -419,92 +426,102 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "jiter"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/c0/a3bb4cc13aced219dd18191ea66e874266bd8aa7b96744e495e1c733aa2d/jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4", size = 167094, upload-time = "2025-09-15T09:20:38.212Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262, upload-time = "2025-05-18T19:03:44.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538, upload-time = "2025-05-18T19:03:55.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781, upload-time = "2025-05-18T19:03:59.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176, upload-time = "2025-05-18T19:04:00.305Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/b5/3009b112b8f673e568ef79af9863d8309a15f0a8cdcc06ed6092051f377e/jiter-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb7b377688cc3850bbe5c192a6bd493562a0bc50cbc8b047316428fbae00ada", size = 305510, upload-time = "2025-09-15T09:19:25.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/82/15514244e03b9e71e086bbe2a6de3e4616b48f07d5f834200c873956fb8c/jiter-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1b7cbe3f25bd0d8abb468ba4302a5d45617ee61b2a7a638f63fee1dc086be99", size = 316521, upload-time = "2025-09-15T09:19:27.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/94/7a2e905f40ad2d6d660e00b68d818f9e29fb87ffe82774f06191e93cbe4a/jiter-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a7f0ec81d5b7588c5cade1eb1925b91436ae6726dc2df2348524aeabad5de6", size = 338214, upload-time = "2025-09-15T09:19:28.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/9c/5791ed5bdc76f12110158d3316a7a3ec0b1413d018b41c5ed399549d3ad5/jiter-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07630bb46ea2a6b9c6ed986c6e17e35b26148cce2c535454b26ee3f0e8dcaba1", size = 361280, upload-time = "2025-09-15T09:19:30.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/7f/b7d82d77ff0d2cb06424141000176b53a9e6b16a1125525bb51ea4990c2e/jiter-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7764f27d28cd4a9cbc61704dfcd80c903ce3aad106a37902d3270cd6673d17f4", size = 487895, upload-time = "2025-09-15T09:19:31.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/44/10a1475d46f1fc1fd5cc2e82c58e7bca0ce5852208e0fa5df2f949353321/jiter-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4a6c4a737d486f77f842aeb22807edecb4a9417e6700c7b981e16d34ba7c72", size = 378421, upload-time = "2025-09-15T09:19:32.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/5f/0dc34563d8164d31d07bc09d141d3da08157a68dcd1f9b886fa4e917805b/jiter-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf408d2a0abd919b60de8c2e7bc5eeab72d4dafd18784152acc7c9adc3291591", size = 347932, upload-time = "2025-09-15T09:19:34.612Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/de/b68f32a4fcb7b4a682b37c73a0e5dae32180140cd1caf11aef6ad40ddbf2/jiter-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdef53eda7d18e799625023e1e250dbc18fbc275153039b873ec74d7e8883e09", size = 386959, upload-time = "2025-09-15T09:19:35.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/0a/c08c92e713b6e28972a846a81ce374883dac2f78ec6f39a0dad9f2339c3a/jiter-0.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:53933a38ef7b551dd9c7f1064f9d7bb235bb3168d0fa5f14f0798d1b7ea0d9c5", size = 517187, upload-time = "2025-09-15T09:19:37.426Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/b5/4a283bec43b15aad54fcae18d951f06a2ec3f78db5708d3b59a48e9c3fbd/jiter-0.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11840d2324c9ab5162fc1abba23bc922124fedcff0d7b7f85fffa291e2f69206", size = 509461, upload-time = "2025-09-15T09:19:38.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/a5/f8bad793010534ea73c985caaeef8cc22dfb1fedb15220ecdf15c623c07a/jiter-0.11.0-cp312-cp312-win32.whl", hash = "sha256:4f01a744d24a5f2bb4a11657a1b27b61dc038ae2e674621a74020406e08f749b", size = 206664, upload-time = "2025-09-15T09:19:40.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/42/5823ec2b1469395a160b4bf5f14326b4a098f3b6898fbd327366789fa5d3/jiter-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:29fff31190ab3a26de026da2f187814f4b9c6695361e20a9ac2123e4d4378a4c", size = 203520, upload-time = "2025-09-15T09:19:41.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/c4/d530e514d0f4f29b2b68145e7b389cbc7cac7f9c8c23df43b04d3d10fa3e/jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb", size = 305021, upload-time = "2025-09-15T09:19:43.523Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/77/796a19c567c5734cbfc736a6f987affc0d5f240af8e12063c0fb93990ffa/jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471", size = 314384, upload-time = "2025-09-15T09:19:44.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/9c/824334de0b037b91b6f3fa9fe5a191c83977c7ec4abe17795d3cb6d174cf/jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd", size = 337389, upload-time = "2025-09-15T09:19:46.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/95/ed4feab69e6cf9b2176ea29d4ef9d01a01db210a3a2c8a31a44ecdc68c38/jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921", size = 360519, upload-time = "2025-09-15T09:19:47.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/0c/2ad00f38d3e583caba3909d95b7da1c3a7cd82c0aa81ff4317a8016fb581/jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df", size = 487198, upload-time = "2025-09-15T09:19:49.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/8b/919b64cf3499b79bdfba6036da7b0cac5d62d5c75a28fb45bad7819e22f0/jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982", size = 377835, upload-time = "2025-09-15T09:19:50.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/7f/8ebe15b6e0a8026b0d286c083b553779b4dd63db35b43a3f171b544de91d/jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64", size = 347655, upload-time = "2025-09-15T09:19:51.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/64/332127cef7e94ac75719dda07b9a472af6158ba819088d87f17f3226a769/jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1", size = 386135, upload-time = "2025-09-15T09:19:53.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/c8/557b63527442f84c14774159948262a9d4fabb0d61166f11568f22fc60d2/jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758", size = 516063, upload-time = "2025-09-15T09:19:54.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/13/4164c819df4a43cdc8047f9a42880f0ceef5afeb22e8b9675c0528ebdccd/jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166", size = 508139, upload-time = "2025-09-15T09:19:55.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/70/6e06929b401b331d41ddb4afb9f91cd1168218e3371972f0afa51c9f3c31/jiter-0.11.0-cp313-cp313-win32.whl", hash = "sha256:8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80", size = 206369, upload-time = "2025-09-15T09:19:57.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/0d/8185b8e15de6dce24f6afae63380e16377dd75686d56007baa4f29723ea1/jiter-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6", size = 202538, upload-time = "2025-09-15T09:19:58.35Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/3a/d61707803260d59520721fa326babfae25e9573a88d8b7b9cb54c5423a59/jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33", size = 313737, upload-time = "2025-09-15T09:19:59.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/cc/c9f0eec5d00f2a1da89f6bdfac12b8afdf8d5ad974184863c75060026457/jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03", size = 346183, upload-time = "2025-09-15T09:20:01.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/87/fc632776344e7aabbab05a95a0075476f418c5d29ab0f2eec672b7a1f0ac/jiter-0.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba", size = 204225, upload-time = "2025-09-15T09:20:03.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/3b/e7f45be7d3969bdf2e3cd4b816a7a1d272507cd0edd2d6dc4b07514f2d9a/jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72", size = 304414, upload-time = "2025-09-15T09:20:04.357Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/32/13e8e0d152631fcc1907ceb4943711471be70496d14888ec6e92034e2caf/jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774", size = 314223, upload-time = "2025-09-15T09:20:05.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/7e/abedd5b5a20ca083f778d96bba0d2366567fcecb0e6e34ff42640d5d7a18/jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0", size = 337306, upload-time = "2025-09-15T09:20:06.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/e2/30d59bdc1204c86aa975ec72c48c482fee6633120ee9c3ab755e4dfefea8/jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a", size = 360565, upload-time = "2025-09-15T09:20:08.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/88/567288e0d2ed9fa8f7a3b425fdaf2cb82b998633c24fe0d98f5417321aa8/jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773", size = 486465, upload-time = "2025-09-15T09:20:09.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/6e/7b72d09273214cadd15970e91dd5ed9634bee605176107db21e1e4205eb1/jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7", size = 377581, upload-time = "2025-09-15T09:20:10.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/52/4db456319f9d14deed325f70102577492e9d7e87cf7097bda9769a1fcacb/jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2", size = 347102, upload-time = "2025-09-15T09:20:12.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/b4/433d5703c38b26083aec7a733eb5be96f9c6085d0e270a87ca6482cbf049/jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2", size = 386477, upload-time = "2025-09-15T09:20:13.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/7a/a60bfd9c55b55b07c5c441c5085f06420b6d493ce9db28d069cc5b45d9f3/jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0", size = 516004, upload-time = "2025-09-15T09:20:14.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/46/f8363e5ecc179b4ed0ca6cb0a6d3bfc266078578c71ff30642ea2ce2f203/jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73", size = 507855, upload-time = "2025-09-15T09:20:16.176Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/33/396083357d51d7ff0f9805852c288af47480d30dd31d8abc74909b020761/jiter-0.11.0-cp314-cp314-win32.whl", hash = "sha256:c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2", size = 205802, upload-time = "2025-09-15T09:20:17.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/ab/eb06ca556b2551d41de7d03bf2ee24285fa3d0c58c5f8d95c64c9c3281b1/jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40", size = 313405, upload-time = "2025-09-15T09:20:18.918Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/22/7ab7b4ec3a1c1f03aef376af11d23b05abcca3fb31fbca1e7557053b1ba2/jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406", size = 347102, upload-time = "2025-09-15T09:20:20.16Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.8.2"
|
||||
version = "1.8.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyyaml", marker = "python_full_version < '3.13'" },
|
||||
{ name = "pyyaml-ft", marker = "python_full_version >= '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/89/aa/b52d195b167958fe1bd106a260f64cc80ec384f6ac2a9cda874d8803df06/libcst-1.8.2.tar.gz", hash = "sha256:66e82cedba95a6176194a817be4232c720312f8be6d2c8f3847f3317d95a0c7f", size = 881534, upload-time = "2025-06-13T20:56:37.915Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/55/ca4552d7fe79a91b2a7b4fa39991e8a45a17c8bfbcaf264597d95903c777/libcst-1.8.5.tar.gz", hash = "sha256:e72e1816eed63f530668e93a4c22ff1cf8b91ddce0ec53e597d3f6c53e103ec7", size = 884582, upload-time = "2025-09-26T05:29:44.101Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/2d/8726bf8ea8252e8fd1e48980753eef5449622c5f6cf731102bc43dcdc2c6/libcst-1.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2e8c1dfa854e700fcf6cd79b2796aa37d55697a74646daf5ea47c7c764bac31c", size = 2185942, upload-time = "2025-06-13T20:55:26.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/b3/565d24db8daed66eae7653c1fc1bc97793d49d5d3bcef530450ee8da882c/libcst-1.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b5c57a3c1976c365678eb0730bcb140d40510990cb77df9a91bb5c41d587ba6", size = 2072622, upload-time = "2025-06-13T20:55:27.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/d6/5a433e8a58eeb5c5d46635cfe958d0605f598d87977d4560484e3662d438/libcst-1.8.2-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:0f23409add2aaebbb6d8e881babab43c2d979f051b8bd8aed5fe779ea180a4e8", size = 2402738, upload-time = "2025-06-13T20:55:29.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/e4/0dd752c1880b570118fa91ac127589e6cf577ddcb2eef1aaf8b81ecc3f79/libcst-1.8.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b88e9104c456590ad0ef0e82851d4fc03e9aa9d621fa8fdd4cd0907152a825ae", size = 2219932, upload-time = "2025-06-13T20:55:31.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/bc/fceae243c6a329477ac6d4edb887bcaa2ae7a3686158d8d9b9abb3089c37/libcst-1.8.2-cp312-cp312-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5ba3ea570c8fb6fc44f71aa329edc7c668e2909311913123d0d7ab8c65fc357", size = 2191891, upload-time = "2025-06-13T20:55:33.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/7d/eb341bdc11f1147e7edeccffd0f2f785eff014e72134f5e46067472012b0/libcst-1.8.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:460fcf3562f078781e1504983cb11909eb27a1d46eaa99e65c4b0fafdc298298", size = 2311927, upload-time = "2025-06-13T20:55:34.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/19/78bfc7aa5a542574d2ab0768210d084901dec5fc373103ca119905408cf2/libcst-1.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1381ddbd1066d543e05d580c15beacf671e1469a0b2adb6dba58fec311f4eed", size = 2281098, upload-time = "2025-06-13T20:55:36.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/37/a41788a72dc06ed3566606f7cf50349c9918cee846eeae45d1bac03d54c2/libcst-1.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a70e40ce7600e1b32e293bb9157e9de3b69170e2318ccb219102f1abb826c94a", size = 2387649, upload-time = "2025-06-13T20:55:37.797Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/df/7a49576c9fd55cdfd8bcfb725273aa4ee7dc41e87609f3451a4901d68057/libcst-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:3ece08ba778b6eeea74d9c705e9af2d1b4e915e9bc6de67ad173b962e575fcc0", size = 2094574, upload-time = "2025-06-13T20:55:39.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/60/27381e194d2af08bfd0fed090c905b2732907b69da48d97d86c056d70790/libcst-1.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:5efd1bf6ee5840d1b0b82ec8e0b9c64f182fa5a7c8aad680fbd918c4fa3826e0", size = 1984568, upload-time = "2025-06-13T20:55:41.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/9c/e3d4c7f1eb5c23907f905f84a4da271b60cd15b746ac794d42ea18bb105e/libcst-1.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08e9dca4ab6f8551794ce7ec146f86def6a82da41750cbed2c07551345fa10d3", size = 2185848, upload-time = "2025-06-13T20:55:43.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/e0/635cbb205d42fd296c01ab5cd1ba485b0aee92bffe061de587890c81f1bf/libcst-1.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8310521f2ccb79b5c4345750d475b88afa37bad930ab5554735f85ad5e3add30", size = 2072510, upload-time = "2025-06-13T20:55:45.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/45/8911cfe9413fd690a024a1ff2c8975f060dd721160178679d3f6a21f939e/libcst-1.8.2-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:da2d8b008aff72acd5a4a588491abdda1b446f17508e700f26df9be80d8442ae", size = 2403226, upload-time = "2025-06-13T20:55:46.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/83/819d2b1b1fd870ad34ce4f34ec68704ca69bf48ef2d7665483115f267ec4/libcst-1.8.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:be821d874ce8b26cbadd7277fa251a9b37f6d2326f8b5682b6fc8966b50a3a59", size = 2220669, upload-time = "2025-06-13T20:55:48.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/2f/2c4742bf834f88a9803095915c4f41cafefb7b04bde66ea86f74668b4b7b/libcst-1.8.2-cp313-cp313-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f74b0bc7378ad5afcf25ac9d0367b4dbba50f6f6468faa41f5dfddcf8bf9c0f8", size = 2191919, upload-time = "2025-06-13T20:55:50.092Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/f4/107e13815f1ee5aad642d4eb4671c0273ee737f3832e3dbca9603b39f8d9/libcst-1.8.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b68ea4a6018abfea1f68d50f74de7d399172684c264eb09809023e2c8696fc23", size = 2311965, upload-time = "2025-06-13T20:55:51.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/63/2948b6e4be367ad375d273a8ad00df573029cffe5ac8f6c09398c250de5b/libcst-1.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e264307ec49b2c72480422abafe80457f90b4e6e693b7ddf8a23d24b5c24001", size = 2281704, upload-time = "2025-06-13T20:55:54.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/d3/590cde9c8c386d5f4f05fdef3394c437ea51060478a5141ff4a1f289e747/libcst-1.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5d5519962ce7c72d81888fb0c09e58e308ba4c376e76bcd853b48151063d6a8", size = 2387511, upload-time = "2025-06-13T20:55:55.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/3d/ba5e36c663028043fc607dc33e5c390c7f73136fb15a890fb3710ee9d158/libcst-1.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:b62aa11d6b74ed5545e58ac613d3f63095e5fd0254b3e0d1168fda991b9a6b41", size = 2094526, upload-time = "2025-06-13T20:55:57.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/34/530ca3b972dddad562f266c81190bea29376f8ba70054ea7b45b114504cd/libcst-1.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9c2bd4ac288a9cdb7ffc3229a9ce8027a66a3fd3f2ab9e13da60f5fbfe91f3b2", size = 1984627, upload-time = "2025-06-13T20:55:59.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/9f/491f7b8d9d93444cd9bf711156ee1f122c38d25b903599e363d669acc8ab/libcst-1.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:08a8c7d9922ca6eed24e2c13a3c552b3c186af8fc78e5d4820b58487d780ec19", size = 2175415, upload-time = "2025-06-13T20:56:01.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/fe/4d13437f453f92687246aa7c5138e102ee5186fe96609ee4c598bb9f9ecb/libcst-1.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bba7c2b5063e8ada5a5477f9fa0c01710645426b5a8628ec50d558542a0a292e", size = 2063719, upload-time = "2025-06-13T20:56:02.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/59/758ae142c6607f275269021362b731e0f22ff5c9aa7cc67b0ed3a6bc930f/libcst-1.8.2-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d97c9fe13aacfbefded6861f5200dcb8e837da7391a9bdeb44ccb133705990af", size = 2380624, upload-time = "2025-06-13T20:56:04.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/c5/31d214a0bcb3523243a9b5643b597ff653d6ec9e1f3326cfcc16bcbf185d/libcst-1.8.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d2194ae959630aae4176a4b75bd320b3274c20bef2a5ca6b8d6fc96d3c608edf", size = 2208801, upload-time = "2025-06-13T20:56:06.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/16/a53f852322b266c63b492836a5c4968f192ee70fb52795a79feb4924e9ed/libcst-1.8.2-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0be639f5b2e1999a4b4a82a0f4633969f97336f052d0c131627983589af52f56", size = 2179557, upload-time = "2025-06-13T20:56:09.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/49/12a5664c73107187ba3af14869d3878fca1fd4c37f6fbb9adb943cb7a791/libcst-1.8.2-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6753e50904e05c27915933da41518ecd7a8ca4dd3602112ba44920c6e353a455", size = 2302499, upload-time = "2025-06-13T20:56:10.751Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/46/2d62552a9346a040c045d6619b645d59bb707a586318121f099abd0cd5c4/libcst-1.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:706d07106af91c343150be86caeae1ea3851b74aa0730fcbbf8cd089e817f818", size = 2271070, upload-time = "2025-06-13T20:56:12.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/67/b625fd6ae22575255aade0a24f45e1d430b7e7279729c9c51d4faac982d2/libcst-1.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd4310ea8ddc49cc8872e083737cf806299b17f93159a1f354d59aa08993e876", size = 2380767, upload-time = "2025-06-13T20:56:13.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/84/fb88f2ffdb045ff7323a6c05dd3d243a9eb3cb3517a6269dee43fbfb9990/libcst-1.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:51bbafdd847529e8a16d1965814ed17831af61452ee31943c414cb23451de926", size = 2083403, upload-time = "2025-06-13T20:56:15.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/8f/da755d6d517eb8ec9664afae967b00a9b8dd567bbbb350e261359c1b47fc/libcst-1.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:4f14f5045766646ed9e8826b959c6d07194788babed1e0ba08c94ea4f39517e3", size = 1974355, upload-time = "2025-06-13T20:56:18.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/bb/c7abe0654fcf00292d6959256948ce4ae07785c4f65a45c3e25cc4637074/libcst-1.8.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c7733aba7b43239157661207b1e3a9f3711a7fc061a0eca6a33f0716fdfd21", size = 2196690, upload-time = "2025-09-26T05:28:17.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/25/e7c02209e8ce66e7b75a66d132118f6f812a8b03cd31ee7d96de56c733a1/libcst-1.8.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b8c3cfbbf6049e3c587713652e4b3c88cfbf7df7878b2eeefaa8dd20a48dc607", size = 2082616, upload-time = "2025-09-26T05:28:19.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/68/a4f49d99e3130256e225d639722440ba2682c12812a30ebd7ba64fd0fd31/libcst-1.8.5-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:31d86025d8997c853f85c4b5d494f04a157fb962e24f187b4af70c7755c9b27d", size = 2229037, upload-time = "2025-09-26T05:28:21.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/62/4fa21600a0bf3eb9f4d4f8bbb50ef120fb0b2990195eabba997b0b889566/libcst-1.8.5-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff9c535cfe99f0be79ac3024772b288570751fc69fc472b44fca12d1912d1561", size = 2292806, upload-time = "2025-09-26T05:28:23.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/df/a01e8d54b62060698e37e3e28f77559ecb70c7b93ffee00d17e40221f419/libcst-1.8.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e8204607504563d3606bbaea2b9b04e0cef2b3bdc14c89171a702c1e09b9318a", size = 2294836, upload-time = "2025-09-26T05:28:24.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/4f/c410e7f7ceda0558f688c1ca5dfb3a40ff8dfc527f8e6015fa749e11a650/libcst-1.8.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5e6cd3df72d47701b205fa3349ba8899566df82cef248c2fdf5f575d640419c4", size = 2396004, upload-time = "2025-09-26T05:28:26.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/07/bb77dcb94badad0ad3e5a1e992a4318dbdf40632eac3b5cf18299858ad7d/libcst-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:197c2f86dd0ca5c6464184ddef7f6440d64c8da39b78d16fc053da6701ed1209", size = 2107301, upload-time = "2025-09-26T05:28:28.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/70/e688e6d99d6920c3f97bf8bbaec33ac2c71a947730772a1d32dd899dbbf1/libcst-1.8.5-cp312-cp312-win_arm64.whl", hash = "sha256:c5ca109c9a81dff3d947dceba635a08f9c3dfeb7f61b0b824a175ef0a98ea69b", size = 1990870, upload-time = "2025-09-26T05:28:29.858Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/77/ca1d2499881c774121ebb7c78c22f371c179f18317961e1e529dafc1af52/libcst-1.8.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9e9563dcd754b65557ba9cdff9a5af32cfa5f007be0db982429580db45bfe", size = 2196687, upload-time = "2025-09-26T05:28:31.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/1c/fdb7c226ad82fcf3b1bb19c24d8e895588a0c1fd2bc81e30792d041e15bc/libcst-1.8.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61d56839d237e9bf3310e6479ffaf6659f298940f0e0d2460ce71ee67a5375df", size = 2082639, upload-time = "2025-09-26T05:28:33.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/1a/c6e89455483355971d13f6d71ad717624686b50558f7e2c12393c2c8e2f1/libcst-1.8.5-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b084769dcda2036265fc426eec5894c658af8d4b0e0d0255ab6bb78c8c9d6eb4", size = 2229202, upload-time = "2025-09-26T05:28:35.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/9c/3e4ce737a34c0ada15a35f51d0dbd8bf0ac0cef0c4560ddc0a8364e3f712/libcst-1.8.5-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c20384b8a4a7801b4416ef96173f1fbb7fafad7529edfdf151811ef70423118a", size = 2293220, upload-time = "2025-09-26T05:28:37.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/74/a68fcb3625b0c218c01aaefef9366f505654a1aa64af99cfe7ff7c97bf41/libcst-1.8.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:271b0b363972ff7d2b8116add13977e7c3b2668c7a424095851d548d222dab18", size = 2295146, upload-time = "2025-09-26T05:28:39.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/c3/f4b6edf204f919c6968eb2d111c338098aebbe3fb5d5d95aceacfcf65d9a/libcst-1.8.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ba728c7aee73b330f49f2df0f0b56b74c95302eeb78860f8d5ff0e0fc52c887", size = 2396597, upload-time = "2025-09-26T05:28:41.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/94/b5cbe122db8f60e7e05bd56743f91d176f3da9b2101f8234e25bb3c5e493/libcst-1.8.5-cp313-cp313-win_amd64.whl", hash = "sha256:0abf0e87570cd3b06a8cafbb5378a9d1cbf12e4583dc35e0fff2255100da55a1", size = 2107479, upload-time = "2025-09-26T05:28:43.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/4d/5e47752c37b33ea6fd1fac76f62e2caa37a6f78d841338bb8fd3dcf51498/libcst-1.8.5-cp313-cp313-win_arm64.whl", hash = "sha256:757390c3cf0b45d7ae1d1d4070c839b082926e762e65eab144f37a63ad33b939", size = 1990992, upload-time = "2025-09-26T05:28:44.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/df/d0eaaed2c402f945fd049b990c98242cb6eace640258e9f8d484206a9666/libcst-1.8.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f8934763389cd21ce3ed229b63b994b79dac8be7e84a9da144823f46bc1ffc5c", size = 2187746, upload-time = "2025-09-26T05:28:46.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/05/ca62c80dc5f2cf26c2d5d1428612950c6f04df66f765ab0ca8b7d42b4ba1/libcst-1.8.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b873caf04862b6649a2a961fce847f7515ba882be02376a924732cf82c160861", size = 2072530, upload-time = "2025-09-26T05:28:48.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/38/34a5825bd87badaf8bc0725e5816d395f43ea2f8d1f3cb6982cccc70a1a2/libcst-1.8.5-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:50e095d18c4f76da0e03f25c50b52a2999acbcbe4598a3cf41842ee3c13b54f1", size = 2219819, upload-time = "2025-09-26T05:28:50.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/ea/10407cc1c06231079f5ee6c5e2c2255a2c3f876a7a7f13af734f9bb6ee0e/libcst-1.8.5-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a3c967725cc3e8fa5c7251188d57d48eec8835f44c6b53f7523992bec595fa0", size = 2283011, upload-time = "2025-09-26T05:28:51.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/fc/c4e4c03b4804ac78b8209e83a3c15e449aa68ddd0e602d5c2cc4b7e1b9ed/libcst-1.8.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eed454ab77f4b18100c41d8973b57069e503943ea4e5e5bbb660404976a0fe7a", size = 2283315, upload-time = "2025-09-26T05:28:53.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/39/75e07c2933b55815b71b1971e5388a24d1d1475631266251249eaed8af28/libcst-1.8.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:39130e59868b8fa49f6eeedd46f008d3456fc13ded57e1c85b211636eb6425f3", size = 2387279, upload-time = "2025-09-26T05:28:54.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/44/0315fb0f2ee8913d209a5caf57932db8efb3f562dbcdc5fb157de92fb098/libcst-1.8.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a7b1cc3abfdba5ce36907f94f07e079528d4be52c07dfffa26f0e68eb1d25d45", size = 2098827, upload-time = "2025-09-26T05:28:56.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/c2/1335fe9feb7d75526df454a8f9db77615460c69691c27af0a57621ca9e47/libcst-1.8.5-cp313-cp313t-win_arm64.whl", hash = "sha256:20354c4217e87afea936e9ea90c57fe0b2c5651f41b3ee59f5df8a53ab417746", size = 1979853, upload-time = "2025-09-26T05:28:58.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/4e/4d961f15e7cc3f9924c4865158cf23de3cb1d9727be5bc5ec1f6b2e0e991/libcst-1.8.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f350ff2867b3075ba97a022de694f2747c469c25099216cef47b58caaee96314", size = 2196843, upload-time = "2025-09-26T05:29:00.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/b5/706b51025218b31346335c8aa1e316e91dbd82b9bd60483a23842a59033b/libcst-1.8.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b95db09d04d125619a63f191c9534853656c4c76c303b8b4c5f950c8e610fba", size = 2082306, upload-time = "2025-09-26T05:29:02.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/78/53816b76257d9d149f074ac0b913be1c94d54fb07b3a77f3e11333659d36/libcst-1.8.5-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:60e62e966b45b7dee6f0ec0fd7687704d29be18ae670c5bc6c9c61a12ccf589f", size = 2230603, upload-time = "2025-09-26T05:29:04.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/06/4497c456ad0ace0f60a38f0935d6e080600532bcddeaf545443d4d7c4db2/libcst-1.8.5-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:7cbb330a352dde570059c73af7b7bbfaa84ae121f54d2ce46c5530351f57419d", size = 2293110, upload-time = "2025-09-26T05:29:05.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/fc/9ef8cc7c0a9cca722b6f176cc82b5925dbcdfcee6e17cd6d3056d45af38e/libcst-1.8.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71b2b1ef2305cba051252342a1a4f8e94e6b8e95d7693a7c15a00ce8849ef722", size = 2296366, upload-time = "2025-09-26T05:29:07.451Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/7e/799dac0cd086cc5dab3837ead9c72dd4e29a79323795dc52b2ebb3aac9a0/libcst-1.8.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0f504d06dfba909d1ba6a4acf60bfe3f22275444d6e0d07e472a5da4a209b0be", size = 2397188, upload-time = "2025-09-26T05:29:09.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/5c/e4f32439818db04ea43b1d6de1d375dcdd5ff33b828864900c340f26436c/libcst-1.8.5-cp314-cp314-win_amd64.whl", hash = "sha256:c69d2b39e360dea5490ccb5dcf5957dcbb1067d27dc1f3f0787d4e287f7744e2", size = 2183599, upload-time = "2025-09-26T05:29:11.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/f9/a457c3da610aef4b5f5c00f1feb67192594b77fb9dddab8f654161c1ea6f/libcst-1.8.5-cp314-cp314-win_arm64.whl", hash = "sha256:63405cb548b2d7b78531535a7819231e633b13d3dee3eb672d58f0f3322892ca", size = 2071025, upload-time = "2025-09-26T05:29:12.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/b6/37abad6fc44df268cd8c2a903ddb2108bd8ac324ef000c2dfcb03d763a41/libcst-1.8.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8a5921105610f35921cc4db6fa5e68e941c6da20ce7f9f93b41b6c66b5481353", size = 2187762, upload-time = "2025-09-26T05:29:14.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/19/d1118c0b25612a3f50fb2c4b2010562fbf7e7df30ad821bab0aae9cf7e4f/libcst-1.8.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:abded10e8d92462fa982d19b064c6f24ed7ead81cf3c3b71011e9764cb12923d", size = 2072565, upload-time = "2025-09-26T05:29:16.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/c8/f72515e2774234c4f92909222d762789cc4be2247ed4189bc0639ade1f8c/libcst-1.8.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dd7bdb14545c4b77a6c0eb39c86a76441fe833da800f6ca63e917e1273621029", size = 2219884, upload-time = "2025-09-26T05:29:18.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/b8/b267b28cbb0cae19e8c7887cdeda72288ae1020d1c22b6c9955f065b296e/libcst-1.8.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6dc28d33ab8750a84c28b5625f7916846ecbecefd89bf75a5292a35644b6efbd", size = 2282790, upload-time = "2025-09-26T05:29:19.578Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/8a/46f2b01bb6782dbc0f4e917ed029b1236278a5dc6d263e55ee986a83a88e/libcst-1.8.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:970b7164a71c65e13c961965f9677bbbbeb21ce2e7e6655294f7f774156391c4", size = 2283591, upload-time = "2025-09-26T05:29:21.024Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ca/3097729b5f6ab1d5e3a753492912d1d8b483a320421d3c0e9e26f1ecef0c/libcst-1.8.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd74c543770e6a61dcb8846c9689dfcce2ad686658896f77f3e21b6ce94bcb2e", size = 2386780, upload-time = "2025-09-26T05:29:22.922Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/cc/4fc91968779b70429106797ddb2265a18b0026e17ec6ba805c34427d2fb9/libcst-1.8.5-cp314-cp314t-win_amd64.whl", hash = "sha256:3d8e80cd1ed6577166f0bab77357f819f12564c2ed82307612e2bcc93e684d72", size = 2174807, upload-time = "2025-09-26T05:29:24.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/3c/db47e1cf0c98a13cbea2cb5611e7b6913ac5e63845b0e41ee7020b03f523/libcst-1.8.5-cp314-cp314t-win_arm64.whl", hash = "sha256:a026aaa19cb2acd8a4d9e2a215598b0a7e2c194bf4482eb9dec4d781ec6e10b2", size = 2059048, upload-time = "2025-09-26T05:29:28.425Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -530,34 +547,34 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.17.1"
|
||||
version = "1.18.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -571,7 +588,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.99.3"
|
||||
version = "1.109.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -583,9 +600,9 @@ dependencies = [
|
|||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/d3/c372420c8ca1c60e785fd8c19e536cea8f16b0cfdcdad6458e1d8884f2ea/openai-1.99.3.tar.gz", hash = "sha256:1a0e2910e4545d828c14218f2ac3276827c94a043f5353e43b9413b38b497897", size = 504932, upload-time = "2025-08-07T20:35:15.893Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133, upload-time = "2025-09-24T13:00:53.075Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/92/bc/e52f49940b4e320629da7db09c90a2407a48c612cff397b4b41b7e58cdf9/openai-1.99.3-py3-none-any.whl", hash = "sha256:c786a03f6cddadb5ee42c6d749aa4f6134fe14fdd7d69a667e5e7ce7fd29a719", size = 785776, upload-time = "2025-08-07T20:35:13.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627, upload-time = "2025-09-24T13:00:50.754Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -599,11 +616,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -629,11 +646,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.8"
|
||||
version = "4.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -664,14 +681,14 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.51"
|
||||
version = "3.0.52"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wcwidth" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -725,7 +742,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.7"
|
||||
version = "2.11.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
|
|
@ -733,9 +750,9 @@ dependencies = [
|
|||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -791,7 +808,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.1"
|
||||
version = "8.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
|
|
@ -800,21 +817,22 @@ dependencies = [
|
|||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -852,28 +870,48 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
version = "6.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -902,7 +940,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.4"
|
||||
version = "2.32.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
|
|
@ -910,47 +948,48 @@ dependencies = [
|
|||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.8"
|
||||
version = "0.13.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373, upload-time = "2025-08-07T19:05:47.268Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/df/8d7d8c515d33adfc540e2edf6c6021ea1c5a58a678d8cfce9fae59aabcab/ruff-0.13.2.tar.gz", hash = "sha256:cb12fffd32fb16d32cef4ed16d8c7cdc27ed7c944eaa98d99d01ab7ab0b710ff", size = 5416417, upload-time = "2025-09-25T14:54:09.936Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315, upload-time = "2025-08-07T19:05:06.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653, upload-time = "2025-08-07T19:05:09.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690, upload-time = "2025-08-07T19:05:12.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923, upload-time = "2025-08-07T19:05:14.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612, upload-time = "2025-08-07T19:05:16.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745, upload-time = "2025-08-07T19:05:18.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885, upload-time = "2025-08-07T19:05:21.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381, upload-time = "2025-08-07T19:05:23.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271, upload-time = "2025-08-07T19:05:25.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783, upload-time = "2025-08-07T19:05:28.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672, upload-time = "2025-08-07T19:05:30.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626, upload-time = "2025-08-07T19:05:32.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162, upload-time = "2025-08-07T19:05:34.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212, upload-time = "2025-08-07T19:05:36.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382, upload-time = "2025-08-07T19:05:38.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482, upload-time = "2025-08-07T19:05:40.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718, upload-time = "2025-08-07T19:05:42.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/84/5716a7fa4758e41bf70e603e13637c42cfb9dbf7ceb07180211b9bbf75ef/ruff-0.13.2-py3-none-linux_armv6l.whl", hash = "sha256:3796345842b55f033a78285e4f1641078f902020d8450cade03aad01bffd81c3", size = 12343254, upload-time = "2025-09-25T14:53:27.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/77/c7042582401bb9ac8eff25360e9335e901d7a1c0749a2b28ba4ecb239991/ruff-0.13.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ff7e4dda12e683e9709ac89e2dd436abf31a4d8a8fc3d89656231ed808e231d2", size = 13040891, upload-time = "2025-09-25T14:53:31.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/15/125a7f76eb295cb34d19c6778e3a82ace33730ad4e6f28d3427e134a02e0/ruff-0.13.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c75e9d2a2fafd1fdd895d0e7e24b44355984affdde1c412a6f6d3f6e16b22d46", size = 12243588, upload-time = "2025-09-25T14:53:33.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/eb/0093ae04a70f81f8be7fd7ed6456e926b65d238fc122311293d033fdf91e/ruff-0.13.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cceac74e7bbc53ed7d15d1042ffe7b6577bf294611ad90393bf9b2a0f0ec7cb6", size = 12491359, upload-time = "2025-09-25T14:53:35.892Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/fe/72b525948a6956f07dad4a6f122336b6a05f2e3fd27471cea612349fedb9/ruff-0.13.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae3f469b5465ba6d9721383ae9d49310c19b452a161b57507764d7ef15f4b07", size = 12162486, upload-time = "2025-09-25T14:53:38.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/e3/0fac422bbbfb2ea838023e0d9fcf1f30183d83ab2482800e2cb892d02dfe/ruff-0.13.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f8f9e3cd6714358238cd6626b9d43026ed19c0c018376ac1ef3c3a04ffb42d8", size = 13871203, upload-time = "2025-09-25T14:53:41.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/82/b721c8e3ec5df6d83ba0e45dcf00892c4f98b325256c42c38ef136496cbf/ruff-0.13.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c6ed79584a8f6cbe2e5d7dbacf7cc1ee29cbdb5df1172e77fbdadc8bb85a1f89", size = 14929635, upload-time = "2025-09-25T14:53:43.953Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/a0/ad56faf6daa507b83079a1ad7a11694b87d61e6bf01c66bd82b466f21821/ruff-0.13.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aed130b2fde049cea2019f55deb939103123cdd191105f97a0599a3e753d61b0", size = 14338783, upload-time = "2025-09-25T14:53:46.205Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/77/ad1d9156db8f99cd01ee7e29d74b34050e8075a8438e589121fcd25c4b08/ruff-0.13.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1887c230c2c9d65ed1b4e4cfe4d255577ea28b718ae226c348ae68df958191aa", size = 13355322, upload-time = "2025-09-25T14:53:48.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/8b/e87cfca2be6f8b9f41f0bb12dc48c6455e2d66df46fe61bb441a226f1089/ruff-0.13.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bcb10276b69b3cfea3a102ca119ffe5c6ba3901e20e60cf9efb53fa417633c3", size = 13354427, upload-time = "2025-09-25T14:53:50.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/df/bf382f3fbead082a575edb860897287f42b1b3c694bafa16bc9904c11ed3/ruff-0.13.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:afa721017aa55a555b2ff7944816587f1cb813c2c0a882d158f59b832da1660d", size = 13537637, upload-time = "2025-09-25T14:53:52.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/70/1fb7a7c8a6fc8bd15636288a46e209e81913b87988f26e1913d0851e54f4/ruff-0.13.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1dbc875cf3720c64b3990fef8939334e74cb0ca65b8dbc61d1f439201a38101b", size = 12340025, upload-time = "2025-09-25T14:53:54.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/27/1e5b3f1c23ca5dd4106d9d580e5c13d9acb70288bff614b3d7b638378cc9/ruff-0.13.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939a1b2a960e9742e9a347e5bbc9b3c3d2c716f86c6ae273d9cbd64f193f22", size = 12133449, upload-time = "2025-09-25T14:53:57.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/09/b92a5ccee289f11ab128df57d5911224197d8d55ef3bd2043534ff72ca54/ruff-0.13.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:50e2d52acb8de3804fc5f6e2fa3ae9bdc6812410a9e46837e673ad1f90a18736", size = 13051369, upload-time = "2025-09-25T14:53:59.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/99/26c9d1c7d8150f45e346dc045cc49f23e961efceb4a70c47dea0960dea9a/ruff-0.13.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3196bc13ab2110c176b9a4ae5ff7ab676faaa1964b330a1383ba20e1e19645f2", size = 13523644, upload-time = "2025-09-25T14:54:01.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/00/e7f1501e81e8ec290e79527827af1d88f541d8d26151751b46108978dade/ruff-0.13.2-py3-none-win32.whl", hash = "sha256:7c2a0b7c1e87795fec3404a485096bcd790216c7c146a922d121d8b9c8f1aaac", size = 12245990, upload-time = "2025-09-25T14:54:03.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/bd/d9f33a73de84fafd0146c6fba4f497c4565fe8fa8b46874b8e438869abc2/ruff-0.13.2-py3-none-win_amd64.whl", hash = "sha256:17d95fb32218357c89355f6f6f9a804133e404fc1f65694372e02a557edf8585", size = 13324004, upload-time = "2025-09-25T14:54:06.05Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/12/28fa2f597a605884deb0f65c1b1ae05111051b2a7030f5d8a4ff7f4599ba/ruff-0.13.2-py3-none-win_arm64.whl", hash = "sha256:da711b14c530412c827219312b7d7fbb4877fb31150083add7e8c5336549cea7", size = 12484437, upload-time = "2025-09-25T14:54:08.022Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.34.1"
|
||||
version = "2.39.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/38/10d6bfe23df1bfc65ac2262ed10b45823f47f810b0057d3feeea1ca5c7ed/sentry_sdk-2.34.1.tar.gz", hash = "sha256:69274eb8c5c38562a544c3e9f68b5be0a43be4b697f5fd385bf98e4fbe672687", size = 336969, upload-time = "2025-07-30T11:13:37.93Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/72/43294fa4bdd75c51610b5104a3ff834459ba653abb415150aa7826a249dd/sentry_sdk-2.39.0.tar.gz", hash = "sha256:8c185854d111f47f329ab6bc35993f28f7a6b7114db64aa426b326998cfa14e9", size = 348556, upload-time = "2025-09-25T09:15:39.064Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/3e/bb34de65a5787f76848a533afbb6610e01fbcdd59e76d8679c254e02255c/sentry_sdk-2.34.1-py2.py3-none-any.whl", hash = "sha256:b7a072e1cdc5abc48101d5146e1ae680fa81fe886d8d95aaa25a0b450c818d32", size = 357743, upload-time = "2025-07-30T11:13:36.145Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/44/4356cc64246ba7b2b920f7c97a85c3c52748e213e250b512ee8152eb559d/sentry_sdk-2.39.0-py2.py3-none-any.whl", hash = "sha256:ba655ca5e57b41569b18e2a5552cb3375209760a5d332cdd87c6c3f28f729602", size = 370851, upload-time = "2025-09-25T09:15:36.35Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -1040,11 +1079,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "types-docutils"
|
||||
version = "0.21.0.20250728"
|
||||
version = "0.22.2.20250924"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/9c/223feef75550722e428bed338bff6d1692e1f32f0e3484a9c5c15a0bc601/types_docutils-0.21.0.20250728.tar.gz", hash = "sha256:fbfe44496c98c71437cd9ac20d71df2ea44878084f604960af7cf3696d562bab", size = 54656, upload-time = "2025-07-28T03:29:16.386Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/6d/60326ba08f44629f778937d5021a342da996682d932261d48b4043c437f7/types_docutils-0.22.2.20250924.tar.gz", hash = "sha256:a13fb412676c164edec7c2f26fe52ab7b0b7c868168dacc4298f6a8069298f3d", size = 56679, upload-time = "2025-09-24T02:53:26.251Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/91/45beab84b3484446d4966deca4606c3e8427af168677776ebafaaf277690/types_docutils-0.21.0.20250728-py3-none-any.whl", hash = "sha256:48b8caaac0d572295f94865b7f97a93a78a305625e7c0d10ea289f3fc15d2fe5", size = 89533, upload-time = "2025-07-28T03:29:15.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/2b/844f3a6e972515ef0890fd8bf631890b6d74c8eacb1acbf31a72820c3b45/types_docutils-0.22.2.20250924-py3-none-any.whl", hash = "sha256:a6d52e21fa70998d34d13db6891ea35920bbb20f91459ca528a3845fd0b9ec03", size = 91873, upload-time = "2025-09-24T02:53:24.824Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1062,71 +1101,71 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "types-greenlet"
|
||||
version = "3.2.0.20250417"
|
||||
version = "3.2.0.20250915"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/a3/0a2583e1542bd79cfdfd3ffc0407c9f0de5f62642cff13f4d191e1291e40/types_greenlet-3.2.0.20250417.tar.gz", hash = "sha256:eb006afcf281ec5756a75c1fd4a6c8a7be5d0cc09b2e82c4856c764760cfa0e3", size = 8785, upload-time = "2025-04-17T02:58:13.007Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/d7/a76e2cb6fdbc2ecaf37a330fe3168d8d89c5f64b939da78fc62c89febab5/types_greenlet-3.2.0.20250915.tar.gz", hash = "sha256:831a390b1d4789b173b067ac546a4024fa9c43825db9f66194916dd0c85e3925", size = 8861, upload-time = "2025-09-15T03:01:18.104Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/ce/61b00f3ffc1ab468d6d994c0880f1470bc0ee9b198dae9fbd773a0029c9f/types_greenlet-3.2.0.20250417-py3-none-any.whl", hash = "sha256:7798b9fdf19d718a62e2d63351e112e7bee622898c6e6cec539296c3dec27808", size = 8819, upload-time = "2025-04-17T02:58:11.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/dd/4fe5803ea70ceada70746d9e90045d673a49093907c7a6108650de59d257/types_greenlet-3.2.0.20250915-py3-none-any.whl", hash = "sha256:59c8866b6ef8b1c62e289c01c90c8544aef37c610bfa13f93099d992327ae1c7", size = 8809, upload-time = "2025-09-15T03:01:17.327Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pexpect"
|
||||
version = "4.9.0.20250516"
|
||||
version = "4.9.0.20250916"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/92/a3/3943fcb94c12af29a88c346b588f1eda180b8b99aeb388a046b25072732c/types_pexpect-4.9.0.20250516.tar.gz", hash = "sha256:7baed9ee566fa24034a567cbec56a5cff189a021344e84383b14937b35d83881", size = 13285, upload-time = "2025-05-16T03:08:33.327Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0c/e6/cc43e306dc7de14ec7861c24ac4957f688741ae39ae685049695d796b587/types_pexpect-4.9.0.20250916.tar.gz", hash = "sha256:69e5fed6199687a730a572de780a5749248a4c5df2ff1521e194563475c9928d", size = 13322, upload-time = "2025-09-16T02:49:25.61Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/d4/3128ae3365b46b9c4a33202af79b0e0d9d4308a6348a3317ce2331fea6cb/types_pexpect-4.9.0.20250516-py3-none-any.whl", hash = "sha256:84cbd7ae9da577c0d2629d4e4fd53cf074cd012296e01fd4fa1031e01973c28a", size = 17081, upload-time = "2025-05-16T03:08:32.127Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/6d/7740e235a9fb2570968da7d386d7feb511ce68cd23472402ff8cdf7fc78f/types_pexpect-4.9.0.20250916-py3-none-any.whl", hash = "sha256:7fa43cb96042ac58bc74f7c28e5d85782be0ee01344149886849e9d90936fe8a", size = 17057, upload-time = "2025-09-16T02:49:24.546Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-psutil"
|
||||
version = "7.0.0.20250801"
|
||||
version = "7.0.0.20251001"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/5d/32fe570f7e22bf638a49c881c5e2142beeda9dad6b21a15805af66571cd8/types_psutil-7.0.0.20250801.tar.gz", hash = "sha256:0230b56234252cc6f59c361dccbaaa08f3088ea3569367abe6900485d388c97d", size = 20238, upload-time = "2025-08-01T03:47:39.309Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/91/b020f9100b196a1f247cd12575f68dcdad94f032c1e0c42987d7632142ce/types_psutil-7.0.0.20251001.tar.gz", hash = "sha256:60d696200ddae28677e7d88cdebd6e960294e85adefbaafe0f6e5d0e7b4c1963", size = 20469, upload-time = "2025-10-01T03:04:21.292Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/84/d18c8d2b53ba2024d110494483b7bdcc9741b7285cd396307b2941353b4d/types_psutil-7.0.0.20250801-py3-none-any.whl", hash = "sha256:751842baf9e0efa31b3a7722a38a3f9afeb5a7132b146a1960cd472db362faa0", size = 23058, upload-time = "2025-08-01T03:47:38.151Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/99/50f30e0b648e6f583165cb2e535b0256a02a03efa4868cb2f017ad25b3d8/types_psutil-7.0.0.20251001-py3-none-any.whl", hash = "sha256:adc31de8386d31c61bd4123112fd51e2c700c7502a001cad72a3d56ba6b463d1", size = 23164, upload-time = "2025-10-01T03:04:20.089Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pygments"
|
||||
version = "2.19.0.20250715"
|
||||
version = "2.19.0.20250809"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "types-docutils" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/e6/5ee8339d4122dccb08cf6b2d766221784960325767cf32a4b9e0aca08cd8/types_pygments-2.19.0.20250715.tar.gz", hash = "sha256:896e45cb5331492be0937b0e9cef976547dee5c507d546a1351289f3cea6eaac", size = 18491, upload-time = "2025-07-15T03:23:28.244Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/1b/a6317763a8f2de01c425644273e5fbe3145d648a081f3bad590b3c34e000/types_pygments-2.19.0.20250809.tar.gz", hash = "sha256:01366fd93ef73c792e6ee16498d3abf7a184f1624b50b77f9506a47ed85974c2", size = 18454, upload-time = "2025-08-09T03:17:14.322Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/48/1b924b3cfbde33b296197615fa5aea5612b59369e5127e8302daec1ea165/types_pygments-2.19.0.20250715-py3-none-any.whl", hash = "sha256:90b45a484123727e125ffddf0379c627d88401dce768a2555038cb5be98b3f2c", size = 25451, upload-time = "2025-07-15T03:23:26.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/c4/d9f0923a941159664d664a0b714242fbbd745046db2d6c8de6fe1859c572/types_pygments-2.19.0.20250809-py3-none-any.whl", hash = "sha256:8e813e5fc25f741b81cadc1e181d402ebd288e34a9812862ddffee2f2b57db7c", size = 25407, upload-time = "2025-08-09T03:17:13.223Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-setuptools"
|
||||
version = "80.9.0.20250801"
|
||||
version = "80.9.0.20250822"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/a3/a508dcfffbccb1c00a29035e7eff0becc5ff3ec81a83e0ac1fb2235d28bf/types_setuptools-80.9.0.20250801.tar.gz", hash = "sha256:e1e92682fa07226415396bb4e2d31f116a16ffbe583b05b01f9910fcdea3b7e8", size = 41182, upload-time = "2025-08-01T03:47:52.922Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/bd/1e5f949b7cb740c9f0feaac430e301b8f1c5f11a81e26324299ea671a237/types_setuptools-80.9.0.20250822.tar.gz", hash = "sha256:070ea7716968ec67a84c7f7768d9952ff24d28b65b6594797a464f1b3066f965", size = 41296, upload-time = "2025-08-22T03:02:08.771Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/9b/26b569f3291fa8861c72abda899125b5491180ea07433424d808a6810588/types_setuptools-80.9.0.20250801-py3-none-any.whl", hash = "sha256:ec908f825134af3964932e6b011dce90f54c291015139cd9cdf79741b7d31b3c", size = 63212, upload-time = "2025-08-01T03:47:50.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/2d/475bf15c1cdc172e7a0d665b6e373ebfb1e9bf734d3f2f543d668b07a142/types_setuptools-80.9.0.20250822-py3-none-any.whl", hash = "sha256:53bf881cb9d7e46ed12c76ef76c0aaf28cfe6211d3fab12e0b83620b1a8642c3", size = 63179, upload-time = "2025-08-22T03:02:07.643Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.14.1"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1162,9 +1201,9 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" },
|
||||
]
|
||||
|
|
|
|||
20
experiments/optimization-factory/.gitignore
vendored
Normal file
20
experiments/optimization-factory/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
dist/
|
||||
build/
|
||||
*.log
|
||||
*.trace
|
||||
k8s/generated-job-*.yaml
|
||||
/work/
|
||||
.env
|
||||
test_key.pem
|
||||
tools/keys
|
||||
server/logs
|
||||
config
|
||||
server/jobs.json
|
||||
config/repos.csv
|
||||
152
experiments/optimization-factory/README.md
Normal file
152
experiments/optimization-factory/README.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
Optimizer Factory for Codeflash — EC2-backed
|
||||
|
||||
What this is
|
||||
|
||||
A minimal pipeline to run Codeflash optimizations across many Python repositories using on-demand EC2 instances. Configure a CSV, launch from the UI, stream logs directly from the instance, and approve results in Codeflash Staging.
|
||||
|
||||
Prerequisites
|
||||
|
||||
- AWS account with permissions for EC2 and IAM
|
||||
- AWS CLI installed and configured (`aws configure` with an IAM user/role)
|
||||
- GitHub Personal Access Token (classic) with `public_repo` scope
|
||||
- Codeflash API key from `app.codeflash.ai`
|
||||
- Python 3.10+
|
||||
|
||||
Project layout
|
||||
|
||||
- scripts/
|
||||
- run_optimization.sh — forks/clones, detects roots, runs Codeflash
|
||||
- detect_roots.py — simple heuristics for module/tests roots
|
||||
- server/
|
||||
- app.py — Flask API serving static UI and EC2 job actions
|
||||
- static/ — plain HTML/CSS/JS UI to manage repos and jobs
|
||||
- analyzer.py — Anthropic-powered analyzer to extract per-repo env config
|
||||
- config/repos.csv — list of repos and resource tiers
|
||||
- tools/requirements.txt — local deps for the server (Flask, boto3, paramiko)
|
||||
- env.example — environment template for EC2 and secrets
|
||||
|
||||
Step-by-step setup
|
||||
|
||||
1. Configure AWS
|
||||
|
||||
- Copy `env.example` to `.env` and fill it (AWS region, EC2 key pair, security group, AMI ID, SSH key path).
|
||||
- **Important for WSL users**: See SSH Key Configuration section below for proper setup.
|
||||
|
||||
2. Install dependencies
|
||||
|
||||
- `pip install -r tools/requirements.txt`
|
||||
|
||||
3. Provide tokens locally
|
||||
|
||||
- Set env vars `CODEFLASH_API_KEY` and `GITHUB_TOKEN` in your shell or `.env`.
|
||||
|
||||
4. Ensure networking
|
||||
|
||||
- The security group must allow outbound HTTPS and inbound SSH from your IP if you want direct access. The instance will reach GitHub and PyPI over the internet.
|
||||
|
||||
5. Configure repositories to process
|
||||
|
||||
- Edit `config/repos.csv` and add rows:
|
||||
|
||||
repo_url,module_root,tests_root,resource_tier
|
||||
https://github.com/psf/requests,requests,tests,small
|
||||
https://github.com/pallets/flask,src/flask,tests,medium
|
||||
https://github.com/numpy/numpy,numpy,numpy/tests,large
|
||||
https://github.com/user/small-util,auto,auto,small
|
||||
|
||||
- Columns:
|
||||
- `repo_url` — upstream repository URL
|
||||
- `module_root`, `tests_root` — path or `auto` to auto-detect
|
||||
- `resource_tier` — `small` | `medium` | `large` (selects job definition)
|
||||
|
||||
6. Run jobs
|
||||
|
||||
- Install local deps:
|
||||
- `pip install -r tools/requirements.txt`
|
||||
- Start server:
|
||||
|
||||
- `python server/app.py`
|
||||
- Open UI: `http://localhost:5000`
|
||||
- From the UI you can:
|
||||
- Add/update/delete repos (edits `config/repos.csv`)
|
||||
- Run optimization for a repo or run all (each launches a dedicated EC2 instance)
|
||||
- Check job status (instance state and exit code)
|
||||
- View logs (tail of `/var/log/codeflash-optimization.log` on the instance)
|
||||
- Analyze a repo via LLM (Anthropic) and apply proposed config to CSV
|
||||
|
||||
7. Monitor and review
|
||||
|
||||
- EC2 console: see instances launching/terminating
|
||||
- UI logs panel: streams the remote log file
|
||||
- Codeflash Staging: approve optimizations
|
||||
|
||||
Retries and tuning
|
||||
|
||||
- If a job fails with OOM, change the `resource_tier` in `config/repos.csv` to a larger tier and re-run the launcher.
|
||||
- For more automation (e.g., automatic tier escalation), consider adding AWS Step Functions later.
|
||||
|
||||
How it works (under the hood)
|
||||
|
||||
- The server launches an EC2 instance per job and waits for SSH.
|
||||
- It uploads `scripts/run_optimization.sh` and `scripts/detect_roots.py`, exports env with analyzer hints, and starts the optimization.
|
||||
- The job writes logs to `/var/log/codeflash-optimization.log`; the server tails this file.
|
||||
- A background watcher terminates the instance after completion.
|
||||
|
||||
Notes
|
||||
|
||||
- Ensure the Codeflash GitHub App is installed for your account/org so forks are covered.
|
||||
- Provide sufficient EC2 instance size; default is `c7i.2xlarge` but adjust as needed.
|
||||
|
||||
LLM-powered Repo Analysis (optional)
|
||||
|
||||
- Purpose: Suggest per-repo configuration (module root, tests root, resource tier) and optional safe setup commands.
|
||||
- Requirements:
|
||||
- `ANTHROPIC_API_KEY` set in environment for the server
|
||||
- `pip install -r tools/requirements.txt` (includes `anthropic`, `jsonschema`)
|
||||
- How it works:
|
||||
- UI → Analyze (🧠) calls `/api/analyze_repo` and shows results once ready
|
||||
- Results are stored as `config/analysis/<org>-<repo>.json`
|
||||
- You can selectively apply `module_root`, `tests_root`, and `resource_tier` to the CSV
|
||||
- On job submit, if analysis exists, BE passes sanitized overrides to the container via env:
|
||||
- `SYSTEM_PACKAGES`: allowlisted apt packages
|
||||
- `PRE_INSTALL_CMDS`, `INSTALL_CMDS`, `POST_INSTALL_CMDS`: safe, filtered commands joined with `&&`
|
||||
- Non-secret env vars if provided
|
||||
- `scripts/entrypoint.sh` executes these overrides before running the default detection path
|
||||
|
||||
SSH Key Configuration
|
||||
|
||||
**Critical for WSL Users**: If you're running this on Windows Subsystem for Linux (WSL), you must configure your SSH key properly to avoid permission errors.
|
||||
|
||||
1. **Copy SSH key to WSL filesystem**:
|
||||
|
||||
```bash
|
||||
# Copy your SSH key from Windows to WSL home directory
|
||||
cp /mnt/c/path/to/your/key.pem ~/.ssh/your_key_name.pem
|
||||
```
|
||||
|
||||
2. **Set correct permissions**:
|
||||
|
||||
```bash
|
||||
# Set restrictive permissions (required by SSH)
|
||||
chmod 600 ~/.ssh/your_key_name.pem
|
||||
```
|
||||
|
||||
3. **Update .env file**:
|
||||
```bash
|
||||
# Use WSL path, not Windows path
|
||||
SSH_KEY_PATH=~/.ssh/your_key_name.pem
|
||||
```
|
||||
|
||||
**Why this is necessary**: SSH requires strict file permissions (600) for private keys. Windows file permissions don't translate correctly to WSL, causing "Permissions are too open" errors. By copying the key to the WSL filesystem and setting permissions with `chmod`, you ensure SSH can read the key properly.
|
||||
|
||||
**Troubleshooting SSH Issues**:
|
||||
|
||||
- If you get "Permissions are too open" error: Ensure the key is in WSL filesystem (`~/.ssh/`) not Windows filesystem (`/mnt/c/`)
|
||||
- If you get "No such file or directory": Verify the path in `.env` matches the actual key location
|
||||
- If you get "Permission denied": Check that `chmod 600` was applied successfully with `ls -la ~/.ssh/`
|
||||
|
||||
Security and safety
|
||||
|
||||
- Commands from LLM are pared down via allowlist; risky patterns are dropped.
|
||||
- Only non-secret env vars are passed through; secrets stay in AWS Secrets Manager.
|
||||
- If analysis is unavailable, the system falls back to current heuristic detection.
|
||||
0
experiments/optimization-factory/__init__.py
Normal file
0
experiments/optimization-factory/__init__.py
Normal file
105
experiments/optimization-factory/env.example
Normal file
105
experiments/optimization-factory/env.example
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Environment for EC2-based Optimizer Factory
|
||||
# Copy this file to .env and fill in your actual values
|
||||
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# AWS CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# AWS_REGION: AWS region where your EC2 instances will run
|
||||
# Used by: server/app.py
|
||||
# Example: us-east-1, us-west-2, eu-west-1
|
||||
AWS_REGION=us-east-1
|
||||
|
||||
## EC2 INSTANCE SETTINGS
|
||||
# AWS_KEY_NAME: Name of your EC2 Key Pair used for SSH
|
||||
AWS_KEY_NAME=your_key_pair_name
|
||||
# AWS_SECURITY_GROUP: Security group ID allowing SSH egress/ingress to instance
|
||||
AWS_SECURITY_GROUP=sg-xxxxxxxx
|
||||
# AWS_INSTANCE_TYPE: Instance type (example: c7i.2xlarge)
|
||||
AWS_INSTANCE_TYPE=c7i.2xlarge
|
||||
# AWS_AMI_ID: Ubuntu 22.04 AMI ID in your region
|
||||
AWS_AMI_ID=ami-xxxxxxxx
|
||||
# SSH_KEY_PATH: Local path to the private key for AWS_KEY_NAME
|
||||
# IMPORTANT: This must be an absolute path or a path that resolves correctly
|
||||
# Examples:
|
||||
# - Linux/macOS: ~/.ssh/your_key_pair.pem or /home/user/.ssh/your_key_pair.pem
|
||||
# - WSL: ~/.ssh/your_key_pair.pem (NOT /mnt/c/path/to/key.pem)
|
||||
# - Windows: C:\Users\YourName\.ssh\your_key_pair.pem
|
||||
#
|
||||
# WSL USERS: You MUST copy your SSH key to the WSL filesystem and set correct permissions:
|
||||
# 1. Copy key: cp /mnt/c/path/to/your/key.pem ~/.ssh/your_key_pair.pem
|
||||
# 2. Set permissions: chmod 600 ~/.ssh/your_key_pair.pem
|
||||
# 3. Use WSL path: SSH_KEY_PATH=~/.ssh/your_key_pair.pem
|
||||
#
|
||||
# The key file must have 600 permissions (readable only by owner) for SSH to work.
|
||||
SSH_KEY_PATH=~/.ssh/your_key_pair.pem
|
||||
|
||||
## (Removed) AWS Batch configuration: no longer used
|
||||
|
||||
# =============================================================================
|
||||
# SERVER CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# PORT: Port number for the Flask web server
|
||||
# Used by: server/app.py to start the web interface
|
||||
# Default: 5000
|
||||
PORT=5000
|
||||
|
||||
# ANTHROPIC_API_KEY: API key for Anthropic Claude LLM analysis
|
||||
# Used by: server/analyzer.py for repository analysis and configuration
|
||||
# Get this from: https://console.anthropic.com/ > API Keys
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
|
||||
# GITHUB_TOKEN: GitHub Personal Access Token for repository access
|
||||
# Used by: server/analyzer.py (for accessing private repos during analysis)
|
||||
# scripts/run_optimization.sh (for GitHub operations during optimization via EC2)
|
||||
# Get this from: GitHub Settings > Developer settings > Personal access tokens
|
||||
# Required scopes: repo (for private repos), public_repo (for public repos)
|
||||
GITHUB_TOKEN=your_github_token_here
|
||||
|
||||
# =============================================================================
|
||||
# TROUBLESHOOTING SSH KEY ISSUES
|
||||
# =============================================================================
|
||||
#
|
||||
# Common SSH key problems and solutions:
|
||||
#
|
||||
# 1. "Permissions are too open" error:
|
||||
# - Problem: SSH key has incorrect permissions
|
||||
# - Solution: Run `chmod 600 ~/.ssh/your_key_pair.pem`
|
||||
# - WSL users: Ensure key is in WSL filesystem, not Windows filesystem
|
||||
#
|
||||
# 2. "No such file or directory" error:
|
||||
# - Problem: SSH_KEY_PATH points to non-existent file
|
||||
# - Solution: Verify the path exists with `ls -la ~/.ssh/your_key_pair.pem`
|
||||
# - Check that the path in .env matches the actual file location
|
||||
#
|
||||
# 3. "Permission denied" error:
|
||||
# - Problem: SSH key permissions are too restrictive or incorrect
|
||||
# - Solution: Run `chmod 600 ~/.ssh/your_key_pair.pem` (not 700 or 644)
|
||||
# - Verify with `ls -la ~/.ssh/your_key_pair.pem` (should show -rw-------)
|
||||
#
|
||||
# 4. WSL-specific issues:
|
||||
# - Problem: Using Windows path (/mnt/c/...) causes permission issues
|
||||
# - Solution: Copy key to WSL filesystem: `cp /mnt/c/path/to/key.pem ~/.ssh/`
|
||||
# - Then set permissions: `chmod 600 ~/.ssh/your_key_pair.pem`
|
||||
# - Update .env to use WSL path: `SSH_KEY_PATH=~/.ssh/your_key_pair.pem`
|
||||
#
|
||||
# 5. Testing SSH connectivity:
|
||||
# - Test manually: `ssh -i ~/.ssh/your_key_pair.pem ubuntu@YOUR_EC2_IP`
|
||||
# - If this works, the issue is in the application configuration
|
||||
# - If this fails, the issue is with the SSH key or EC2 instance setup
|
||||
|
||||
# =============================================================================
|
||||
# NOTES ABOUT REMOTE JOB VARIABLES
|
||||
# =============================================================================
|
||||
# The following variables are injected remotely on EC2 per job:
|
||||
#
|
||||
# GITHUB_REPO_URL: Set by server/app.py when launching the job
|
||||
# MODULE_ROOT: Set by server/app.py
|
||||
# TESTS_ROOT: Set by server/app.py
|
||||
# CODEFLASH_API_KEY: Taken from local environment and exported on the instance
|
||||
# FORK_OWNER: Determined by gh on the instance
|
||||
|
||||
|
||||
0
experiments/optimization-factory/scripts/__init__.py
Normal file
0
experiments/optimization-factory/scripts/__init__.py
Normal file
44
experiments/optimization-factory/scripts/detect_roots.py
Normal file
44
experiments/optimization-factory/scripts/detect_roots.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import os
|
||||
import json
|
||||
|
||||
|
||||
def find_module_root(cwd: str) -> str | None:
|
||||
# Prefer src layout
|
||||
src_dir = os.path.join(cwd, "src")
|
||||
if os.path.isdir(src_dir):
|
||||
for name in os.listdir(src_dir):
|
||||
path = os.path.join(src_dir, name)
|
||||
if os.path.isdir(path) and os.path.isfile(os.path.join(path, "__init__.py")):
|
||||
return os.path.relpath(path, cwd).replace("\\", "/")
|
||||
|
||||
# Fallback: top-level package dir containing __init__.py
|
||||
for name in os.listdir(cwd):
|
||||
if name in {"tests", "test", "benchmarks", "venv", ".venv", "build", "dist", ".git"}:
|
||||
continue
|
||||
path = os.path.join(cwd, name)
|
||||
if os.path.isdir(path) and os.path.isfile(os.path.join(path, "__init__.py")):
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
def find_tests_root(cwd: str) -> str | None:
|
||||
for cand in ("tests", "test", "testing"):
|
||||
if os.path.isdir(os.path.join(cwd, cand)):
|
||||
return cand
|
||||
return None
|
||||
|
||||
|
||||
def main() -> None:
|
||||
cwd = os.getcwd()
|
||||
module_root = find_module_root(cwd)
|
||||
tests_root = find_tests_root(cwd)
|
||||
print(json.dumps({
|
||||
"module_root": module_root or "",
|
||||
"tests_root": tests_root or "",
|
||||
}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
60
experiments/optimization-factory/scripts/entrypoint.sh
Normal file
60
experiments/optimization-factory/scripts/entrypoint.sh
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "--- [ENTRYPOINT] Starting environment setup ---"
|
||||
|
||||
cd /repo
|
||||
if [ -z "${GITHUB_REPO_URL:-}" ]; then echo "GITHUB_REPO_URL is required"; exit 1; fi
|
||||
|
||||
# Optional: run system packages installation if provided (allowlisted by BE)
|
||||
if [ -n "${SYSTEM_PACKAGES:-}" ]; then
|
||||
echo "Installing system packages: ${SYSTEM_PACKAGES}"
|
||||
sudo apt-get update && sudo apt-get install -y --no-install-recommends ${SYSTEM_PACKAGES} || true
|
||||
fi
|
||||
|
||||
echo "Cloning ${GITHUB_REPO_URL} to inspect for dependencies..."
|
||||
git clone "${GITHUB_REPO_URL}" .
|
||||
|
||||
echo "Detecting package manager..."
|
||||
if [ -f "poetry.lock" ]; then
|
||||
echo "Poetry project detected. Installing dependencies with 'poetry install'."
|
||||
poetry install --with dev || poetry install
|
||||
export VENV_PATH=$(poetry env info --path)
|
||||
elif [ -f "pyproject.toml" ] && grep -q "\[tool.uv\]" "pyproject.toml"; then
|
||||
echo "UV project detected. Installing dependencies with 'uv sync'."
|
||||
uv venv
|
||||
uv sync --all-extras || uv sync
|
||||
export VENV_PATH=$(pwd)/.venv
|
||||
elif [ -f "requirements.txt" ]; then
|
||||
echo "requirements.txt detected. Installing with pip."
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt || true
|
||||
[ -f "requirements-test.txt" ] && pip install -r requirements-test.txt || true
|
||||
[ -f "requirements-dev.txt" ] && pip install -r requirements-dev.txt || true
|
||||
export VENV_PATH=$(pwd)/.venv
|
||||
else
|
||||
echo "WARNING: No standard dependency file found. Proceeding without installation."
|
||||
python -m venv .venv
|
||||
export VENV_PATH=$(pwd)/.venv
|
||||
fi
|
||||
|
||||
# Optional: LLM-provided pre/install/post commands (allowlisted and sanitized by BE)
|
||||
run_cmds_if_any() {
|
||||
CMDS_VAR="$1"
|
||||
CMDS_VAL="${!CMDS_VAR}"
|
||||
if [ -n "$CMDS_VAL" ]; then
|
||||
echo "Running commands from $CMDS_VAR"
|
||||
/bin/bash -lc "$CMDS_VAL" || true
|
||||
fi
|
||||
}
|
||||
|
||||
run_cmds_if_any PRE_INSTALL_CMDS
|
||||
run_cmds_if_any INSTALL_CMDS
|
||||
run_cmds_if_any POST_INSTALL_CMDS
|
||||
|
||||
echo "--- [ENTRYPOINT] Handing off to optimization script ---"
|
||||
rm -rf /repo/*
|
||||
/app/scripts/run_optimization.sh
|
||||
|
||||
|
||||
515
experiments/optimization-factory/scripts/llm_setup_helper.py
Normal file
515
experiments/optimization-factory/scripts/llm_setup_helper.py
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
#!/usr/bin/env python3
|
||||
# Shebang line: specifies the interpreter to be used for executing the script (Python 3).
|
||||
import os
|
||||
# Imports the os module for interacting with the operating system, such as file paths and environment variables.
|
||||
import sys
|
||||
# Imports the sys module, providing access to system-specific parameters and functions, like sys.exit.
|
||||
import json
|
||||
# Imports the json module for working with JSON data (encoding and decoding).
|
||||
import subprocess
|
||||
# Imports the subprocess module for running new processes (shell commands).
|
||||
from datetime import datetime
|
||||
# Imports the datetime class from the datetime module, used for generating timestamps.
|
||||
|
||||
|
||||
def sh(cmd: str) -> subprocess.CompletedProcess:
|
||||
# Function to execute a shell command.
|
||||
# cmd: The shell command string to execute.
|
||||
# -> subprocess.CompletedProcess: Returns the result of the executed process.
|
||||
return subprocess.run(
|
||||
cmd, # The command to execute.
|
||||
shell=True, # Execute the command through the shell (allows shell features like piping).
|
||||
stdout=subprocess.PIPE, # Captures standard output.
|
||||
stderr=subprocess.STDOUT, # Directs standard error to standard output (so both are captured in stdout).
|
||||
text=True # Decodes stdout and stderr as text using the default encoding.
|
||||
)
|
||||
|
||||
|
||||
def list_tree(root: str, max_depth: int = 2, max_entries: int = 500) -> str:
|
||||
# Function to generate a simplified file tree structure string for a given root directory.
|
||||
# root: The starting directory path.
|
||||
# max_depth: The maximum directory depth to traverse (default 2).
|
||||
# max_entries: The maximum total number of file entries to include (default 500).
|
||||
# -> str: Returns the file tree as a single string, with lines separated by newlines.
|
||||
out_lines = []
|
||||
# List to store the lines of the file tree output.
|
||||
count = 0
|
||||
# Counter for the number of files added to the output.
|
||||
for cur_root, dirs, files in os.walk(root):
|
||||
# os.walk iterates through the directory tree rooted at 'root'.
|
||||
# cur_root: The current directory path.
|
||||
# dirs: A list of subdirectory names in cur_root.
|
||||
# files: A list of file names in cur_root.
|
||||
|
||||
# Calculate the current depth relative to the initial root.
|
||||
depth = cur_root[len(root):].count(os.sep)
|
||||
# Calculate indentation based on depth.
|
||||
indent = " " * depth
|
||||
|
||||
# Add the current directory name to the output with indentation and a trailing slash.
|
||||
out_lines.append(f"{indent}{os.path.basename(cur_root)}/")
|
||||
|
||||
# Process and list files in the current directory, limiting to the first 200 files in this directory.
|
||||
for f in sorted(files)[:200]:
|
||||
# Check if the maximum total entry count has been reached.
|
||||
if count >= max_entries:
|
||||
# If max entries reached, append a truncation message and return the output.
|
||||
out_lines.append(" ... (truncated)")
|
||||
return "\n".join(out_lines)
|
||||
|
||||
# Add the file name to the output with indentation.
|
||||
out_lines.append(f"{indent} {f}")
|
||||
# Increment the file count.
|
||||
count += 1
|
||||
|
||||
# Prune the directory list if the maximum depth is reached.
|
||||
if depth >= max_depth:
|
||||
# Setting dirs[:] = [] prevents os.walk from descending into subdirectories of the current directory.
|
||||
dirs[:] = []
|
||||
|
||||
# Join all collected lines into a single string and return it.
|
||||
return "\n".join(out_lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
# Main function to execute the LLM setup helper logic.
|
||||
# -> int: Returns an exit code (0 for success, non-zero for error).
|
||||
|
||||
# Get the Anthropic API key from the environment variable.
|
||||
api_key = os.getenv("ANTHROPIC_API_KEY", "").strip()
|
||||
|
||||
# Check if the API key is available. If not, log a message and exit gracefully (assuming no LLM interaction is possible).
|
||||
if not api_key:
|
||||
print("ANTHROPIC_API_KEY not set; skipping LLM setup helper.")
|
||||
return 0
|
||||
|
||||
# Get work repository path from environment, defaulting to a specific path.
|
||||
work_repo = os.environ.get("WORK_REPO", "/home/ubuntu/work/repo")
|
||||
# Get the root directory for tests from environment variables, preferring LLM_TESTS_ROOT.
|
||||
tests_root = os.environ.get("LLM_TESTS_ROOT") or os.environ.get("TESTS_ROOT") or ""
|
||||
# Get the pytest command from environment variables, defaulting to "pytest".
|
||||
pytest_cmd = os.environ.get("LLM_PYTEST_CMD") or os.environ.get("PYTEST_CMD") or "pytest"
|
||||
|
||||
# Define the directory for conversation logs.
|
||||
conv_dir = "/home/ubuntu/app/logs"
|
||||
# Create the log directory if it doesn't exist.
|
||||
os.makedirs(conv_dir, exist_ok=True)
|
||||
|
||||
# Generate an ISO-formatted timestamp for the log file name.
|
||||
ts = datetime.utcnow().isoformat(timespec="seconds").replace(":", "-")
|
||||
# Define the path for the specific log file using the timestamp.
|
||||
conv_file = os.path.join(conv_dir, f"llm-setup-{ts}.log")
|
||||
# Define the path for a symbolic link that points to the latest log file.
|
||||
symlink_path = os.path.join(conv_dir, "llm-setup.log")
|
||||
|
||||
# --- Symlink Creation ---
|
||||
try:
|
||||
# Check if the symlink path exists (either as a link or a regular file/dir).
|
||||
if os.path.islink(symlink_path) or os.path.exists(symlink_path):
|
||||
try:
|
||||
# Attempt to delete the existing symlink or file/directory.
|
||||
os.unlink(symlink_path)
|
||||
except Exception:
|
||||
# Ignore errors during unlinking.
|
||||
pass
|
||||
# Create a new symbolic link from symlink_path to conv_file.
|
||||
os.symlink(conv_file, symlink_path)
|
||||
except Exception:
|
||||
# Ignore errors during symlink creation (e.g., permission issues).
|
||||
pass
|
||||
# --- End Symlink Creation ---
|
||||
|
||||
# Derive repo slug for memory files from GITHUB_REPO_URL if available, else work repo basename
|
||||
repo_url_for_slug = os.environ.get("GITHUB_REPO_URL", "").strip()
|
||||
slug = None
|
||||
if repo_url_for_slug:
|
||||
parts = repo_url_for_slug.rstrip("/").split("/")
|
||||
if len(parts) >= 2:
|
||||
org = parts[-2]
|
||||
name = parts[-1].replace(".git", "")
|
||||
slug = f"{org}-{name}"
|
||||
if not slug:
|
||||
slug = os.path.basename(work_repo.rstrip("/")) or "repo"
|
||||
|
||||
# Session JSONL file for full conversation logging
|
||||
conv_jsonl = os.path.join(conv_dir, f"llm-setup-{ts}.jsonl")
|
||||
# Persistent memory JSONL across runs for this repo
|
||||
memory_file = os.path.join(conv_dir, f"llm-memory-{slug}.jsonl")
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.utcnow().isoformat(timespec="seconds") + "Z"
|
||||
|
||||
def _append_jsonl(path: str, obj: dict) -> None:
|
||||
try:
|
||||
with open(path, "a", encoding="utf-8") as jf:
|
||||
jf.write(json.dumps(obj, ensure_ascii=False) + "\n")
|
||||
except Exception:
|
||||
# JSONL logging errors should never crash the helper
|
||||
pass
|
||||
|
||||
def _load_memory_tail(path: str, max_messages: int = 10) -> list[dict]:
|
||||
try:
|
||||
if not os.path.exists(path):
|
||||
return []
|
||||
with open(path, "r", encoding="utf-8") as jf:
|
||||
lines = jf.readlines()[-max_messages:]
|
||||
out = []
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
role = obj.get("role")
|
||||
content = obj.get("content")
|
||||
if isinstance(role, str) and isinstance(content, str):
|
||||
out.append({"role": role, "content": content})
|
||||
except Exception:
|
||||
continue
|
||||
return out
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def log(msg: str) -> None:
|
||||
# Inner function to append a message to the current log file.
|
||||
with open(conv_file, "a", encoding="utf-8") as f:
|
||||
# Open the log file in append mode ('a') with UTF-8 encoding.
|
||||
f.write(f"[{_now_iso()}] {msg}\n")
|
||||
# Write the message with timestamp followed by a newline.
|
||||
|
||||
# Initial logging of the helper start and key parameters.
|
||||
log("=== LLM Setup Helper Started ===")
|
||||
log(f"Repo: {work_repo}")
|
||||
log(f"Tests root: {tests_root}")
|
||||
log(f"Pytest cmd: {pytest_cmd}")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "start", "repo": work_repo, "tests_root": tests_root, "pytest_cmd": pytest_cmd})
|
||||
|
||||
# Generate the file tree string for the repository.
|
||||
tree = list_tree(work_repo)
|
||||
|
||||
# Prepare Anthropic client lazily (import only when needed, after API key check).
|
||||
try:
|
||||
from anthropic import Anthropic
|
||||
# Attempt to import the Anthropic client library.
|
||||
except Exception as e:
|
||||
# If import fails, log the error and exit with an error code.
|
||||
log(f"Anthropic client import failed: {e}")
|
||||
return 1
|
||||
|
||||
# Initialize the Anthropic client with the retrieved API key.
|
||||
client = Anthropic(api_key=api_key)
|
||||
|
||||
# Define the system prompt, which sets the LLM's role, goal, and output format/rules.
|
||||
system_prompt = (
|
||||
"You are a reliable setup assistant. Your goal: get tests running to at least 50% passing. "
|
||||
"You can only respond with JSON following the schema: {\n"
|
||||
" \"actions\": [ { \"type\": \"shell\", \"cmd\": \"string\" } ... ],\n"
|
||||
" \"comment\": \"brief reasoning\"\n} . "
|
||||
"Rules:\n- Only use safe package commands (pip install <pkg>), avoid destructive ops.\n"
|
||||
"- DO NOT rewrite pyproject.toml or packaging files destructively. Prefer adding missing deps via pip.\n"
|
||||
"- Avoid removing files or changing project layout; prefer the simplest fix.\n"
|
||||
"- Assume venv is active.\n- Prefer installing missing Python packages explicitly.\n"
|
||||
"- Use one or more actions; keep each action minimal.\n"
|
||||
"- You are not allowed to change directories (cd), or use semicolons (;) or pipes (|).\n"
|
||||
"- You MAY chain multiple allowed install commands using '&&' only. Each side of '&&' must be an allowed install command.\n"
|
||||
"- Use the repo venv interpreter for all Python/pip: .venv/bin/python -m pip install <pkgs> (preferred).\n"
|
||||
"- Allowed actions: {type: shell, cmd: '.venv/bin/python -m pip install ...' | 'python -m pip install ...' | 'pip install ...' | 'python devscripts/install_deps.py'} | {type: read, path: 'relative/path'} | {type: finish}.\n"
|
||||
"- Do NOT run tests (pytest) or benchmarks; we will run tests after you finish.\n"
|
||||
"- After you are done with installation steps, respond with a single {\"actions\":[{\"type\":\"finish\"}],\"comment\":\"...\"}.\n"
|
||||
"- We will provide you the remaining tries_left each turn; optimize your plan accordingly.\n"
|
||||
"- If you need to read a small file, request it using: {\"actions\":[{\"type\":\"read\",\"path\":\"relative/path\"}]} (limit to key config/tests).\n"
|
||||
)
|
||||
|
||||
# Prepare the initial context data to send to the LLM.
|
||||
user_context = {
|
||||
"repo_path": work_repo,
|
||||
"tests_root": tests_root,
|
||||
"pytest_cmd": pytest_cmd,
|
||||
"tree": tree,
|
||||
}
|
||||
|
||||
# Initialize the conversation history with memory, then initial context message.
|
||||
chat = []
|
||||
prior_memory = _load_memory_tail(memory_file, max_messages=10)
|
||||
if prior_memory:
|
||||
chat.extend(prior_memory)
|
||||
log(f"Loaded conversation memory: {len(prior_memory)} messages")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "memory_loaded", "count": len(prior_memory)})
|
||||
# Include minimal test tail if available
|
||||
test_tail = ""
|
||||
try:
|
||||
test_log_path = os.environ.get("TEST_LOG_FILE", "").strip()
|
||||
if test_log_path and os.path.isfile(test_log_path):
|
||||
with open(test_log_path, "r", encoding="utf-8", errors="ignore") as tf:
|
||||
content = tf.read()
|
||||
test_tail = content[-4000:]
|
||||
except Exception:
|
||||
test_tail = ""
|
||||
initial_payload = {"context": user_context}
|
||||
if test_tail:
|
||||
initial_payload["last_test_output_tail"] = test_tail
|
||||
initial_payload["tries_left"] = int(os.environ.get("LLM_SETUP_MAX_STEPS", "10") or "10")
|
||||
chat.append({"role": "user", "content": json.dumps(initial_payload, ensure_ascii=False)})
|
||||
|
||||
# Initialize a set to track executed commands for deduplication (guardrail).
|
||||
executed_cmds: set[str] = set()
|
||||
|
||||
def add_msg(role: str, content: str) -> None:
|
||||
# Inner function to add a new message (from user or assistant) to the chat history.
|
||||
chat.append({"role": role, "content": content})
|
||||
# Persist to session JSONL and memory JSONL
|
||||
payload = {"ts": _now_iso(), "role": role, "content": content}
|
||||
_append_jsonl(conv_jsonl, payload)
|
||||
_append_jsonl(memory_file, payload)
|
||||
# Also write a readable line to plaintext log
|
||||
log(f"[{role.upper()}] {content}")
|
||||
# Keep recent window to bound context size for the LLM.
|
||||
if len(chat) > 12:
|
||||
del chat[: len(chat) - 12]
|
||||
|
||||
# Define loop control variables.
|
||||
max_loops = int(os.environ.get("LLM_SETUP_MAX_STEPS", "10") or "10")
|
||||
pass_threshold = 0.5 # Target passing ratio (50%).
|
||||
|
||||
# Start the main loop for setup attempts.
|
||||
for i in range(1, max_loops + 1):
|
||||
# Log the start of the current step.
|
||||
log(f"\n--- Setup step {i}/{max_loops} ---")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "setup_step", "iteration": i, "max": max_loops})
|
||||
|
||||
# Prepare the message content for the LLM, including remaining tries and optional signals from last executions.
|
||||
msg_text = json.dumps({
|
||||
"context": user_context,
|
||||
"tries_left": max_loops - i + 1,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
# Add the new message to the conversation history.
|
||||
add_msg("user", msg_text)
|
||||
|
||||
# --- Call Anthropic API ---
|
||||
try:
|
||||
# Get the model name from the environment, with a default value.
|
||||
model = os.environ.get("ANTHROPIC_MODEL", "claude-3-5-haiku-20241022")
|
||||
# Provide summarized last outputs to reduce tokens
|
||||
# (already appended test output tail above; keep conversation short via truncation in add_msg)
|
||||
resp = client.messages.create(
|
||||
model=model,
|
||||
max_tokens=800, # Limit the LLM's response length.
|
||||
system=system_prompt, # Provide the system prompt.
|
||||
messages=chat, # Send the conversation history.
|
||||
)
|
||||
except Exception as e:
|
||||
# Log API error and exit with an error code.
|
||||
log(f"Anthropic API error: {e}")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "api_error", "error": str(e)})
|
||||
return 1
|
||||
|
||||
# Extract the text content from the LLM response.
|
||||
text = ""
|
||||
try:
|
||||
# Iterate through content blocks (which should contain the JSON response).
|
||||
for block in resp.content:
|
||||
if getattr(block, "type", "text") == "text":
|
||||
text += block.text
|
||||
except Exception:
|
||||
# Fallback for unexpected response structure.
|
||||
text = str(resp)
|
||||
|
||||
# Log the raw text response from the LLM.
|
||||
log("LLM raw response:\n" + text)
|
||||
add_msg("assistant", text)
|
||||
# --- End Anthropic API Call ---
|
||||
|
||||
# Handle optional file read requests from assistant (single small file)
|
||||
try:
|
||||
data_peek = json.loads(text)
|
||||
reqs = data_peek.get("actions") if isinstance(data_peek, dict) else None
|
||||
read_path = None
|
||||
if isinstance(reqs, list):
|
||||
for a in reqs:
|
||||
if isinstance(a, dict) and a.get("type") == "read" and isinstance(a.get("path"), str):
|
||||
read_path = a["path"]
|
||||
break
|
||||
if read_path:
|
||||
# Allowlist: only small text files under repo root
|
||||
safe = True
|
||||
if read_path.startswith("/") or ".." in read_path or len(read_path) > 200:
|
||||
safe = False
|
||||
abs_path = os.path.join(work_repo, read_path)
|
||||
content = ""
|
||||
if safe and os.path.isfile(abs_path) and os.path.getsize(abs_path) <= 200*1024:
|
||||
try:
|
||||
with open(abs_path, "r", encoding="utf-8", errors="ignore") as rf:
|
||||
content = rf.read()
|
||||
except Exception:
|
||||
content = ""
|
||||
# Return file content (summarized if large) as a user message
|
||||
if content:
|
||||
snippet = content if len(content) <= 8000 else (content[:6000] + "\n...\n" + content[-1000:])
|
||||
add_msg("user", json.dumps({"file": read_path, "content": snippet}, ensure_ascii=False))
|
||||
else:
|
||||
add_msg("user", json.dumps({"file": read_path, "error": "unavailable or too large"}, ensure_ascii=False))
|
||||
# Continue to next loop iteration to let model act on the file
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# --- Parse LLM Response and Execute Actions ---
|
||||
# Robust JSON extraction: find the largest JSON object if raw isn't valid
|
||||
def _extract_json(s: str) -> str:
|
||||
try:
|
||||
json.loads(s)
|
||||
return s
|
||||
except Exception:
|
||||
pass
|
||||
# best-effort bracket matching
|
||||
start = s.find('{')
|
||||
last = s.rfind('}')
|
||||
while start != -1 and last != -1 and last > start:
|
||||
candidate = s[start:last+1]
|
||||
try:
|
||||
json.loads(candidate)
|
||||
return candidate
|
||||
except Exception:
|
||||
last = s.rfind('}', 0, last)
|
||||
return ""
|
||||
|
||||
extracted = _extract_json(text)
|
||||
if not extracted:
|
||||
log("Failed to extract JSON from assistant output")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "parse_error", "error": "no_json_in_output", "assistant_text": text[:16000]})
|
||||
return 1
|
||||
|
||||
try:
|
||||
data = json.loads(extracted)
|
||||
except Exception as e:
|
||||
log(f"Failed to parse LLM JSON: {e}")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "parse_error", "error": str(e), "assistant_text": text[:16000]})
|
||||
return 1
|
||||
|
||||
# Extract the 'actions' list from the parsed JSON data.
|
||||
actions = data.get("actions") or []
|
||||
# Validate that 'actions' is a non-empty list.
|
||||
if not isinstance(actions, list) or not actions:
|
||||
log("No actions provided; aborting.")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "no_actions"})
|
||||
return 1
|
||||
|
||||
# Detect finish action
|
||||
if len(actions) == 1 and isinstance(actions[0], dict) and actions[0].get("type") == "finish":
|
||||
log("Assistant requested finish; exiting setup loop.")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "finish_requested", "iteration": i})
|
||||
# Successful finish; orchestrator will re-run tests
|
||||
return 0
|
||||
|
||||
# List to store the summary of executed commands for feedback to the LLM.
|
||||
exec_summ = []
|
||||
# Iterate over the proposed actions.
|
||||
for a in actions:
|
||||
# Validate the action structure
|
||||
if not isinstance(a, dict):
|
||||
continue
|
||||
a_type = a.get("type")
|
||||
if a_type == "shell":
|
||||
# Extract and clean the command string.
|
||||
cmd = str(a.get("cmd") or "").strip()
|
||||
if not cmd:
|
||||
continue
|
||||
|
||||
# Guardrails: determine if this shell command is allowed
|
||||
def _is_allowed_single(c: str) -> bool:
|
||||
lc = c.strip()
|
||||
# Disallow directory changes
|
||||
if lc.startswith("cd ") or " cd " in f" {lc} ":
|
||||
return False
|
||||
# Allow pip installs via multiple forms
|
||||
allowed_prefixes = [
|
||||
"pip install ",
|
||||
"pip3 install ",
|
||||
"python -m pip install ",
|
||||
"python3 -m pip install ",
|
||||
".venv/bin/python -m pip install ",
|
||||
".venv/bin/python3 -m pip install ",
|
||||
]
|
||||
if any(lc.startswith(p) for p in allowed_prefixes):
|
||||
return True
|
||||
# Allow safe pip queries
|
||||
allowed_pip_queries = [
|
||||
"python -m pip list",
|
||||
"python3 -m pip list",
|
||||
".venv/bin/python -m pip list",
|
||||
".venv/bin/python3 -m pip list",
|
||||
"python -m pip show ",
|
||||
"python3 -m pip show ",
|
||||
".venv/bin/python -m pip show ",
|
||||
".venv/bin/python3 -m pip show ",
|
||||
"python -m pip freeze",
|
||||
"python3 -m pip freeze",
|
||||
".venv/bin/python -m pip freeze",
|
||||
".venv/bin/python3 -m pip freeze",
|
||||
]
|
||||
if any(lc.startswith(p) for p in allowed_pip_queries):
|
||||
return True
|
||||
# Allow repo-provided dependency helper when present
|
||||
if lc in {
|
||||
"python devscripts/install_deps.py",
|
||||
"python3 devscripts/install_deps.py",
|
||||
".venv/bin/python devscripts/install_deps.py",
|
||||
".venv/bin/python3 devscripts/install_deps.py",
|
||||
}:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _is_allowed_shell(c: str) -> bool:
|
||||
# Disallow semicolons or pipes entirely
|
||||
if ";" in c or "|" in c:
|
||||
return False
|
||||
# Permit '&&' as a chain of allowed singles
|
||||
parts = [p.strip() for p in c.split("&&")]
|
||||
if len(parts) > 1:
|
||||
return all(_is_allowed_single(p) for p in parts)
|
||||
return _is_allowed_single(c)
|
||||
|
||||
if not _is_allowed_shell(cmd):
|
||||
log(f"Blocked command (unsafe): {cmd}")
|
||||
exec_summ.append({"cmd": cmd, "status": "blocked"})
|
||||
continue
|
||||
if cmd in executed_cmds:
|
||||
log(f"Skipping duplicate command: {cmd}")
|
||||
exec_summ.append({"cmd": cmd, "status": "duplicate_skipped"})
|
||||
continue
|
||||
# Execute the command in repo cwd
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(work_repo)
|
||||
log(f"Executing: {cmd}")
|
||||
r = sh(cmd)
|
||||
log(r.stdout)
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "exec", "cmd": cmd, "stdout": r.stdout[-16000:]})
|
||||
executed_cmds.add(cmd)
|
||||
exec_summ.append({"cmd": cmd, "status": "ran"})
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
elif a_type == "read":
|
||||
# handled earlier in the loop; ignore here
|
||||
continue
|
||||
elif a_type == "finish":
|
||||
log("Assistant issued finish in multi-action response; exiting.")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "finish_requested", "iteration": i})
|
||||
return 0
|
||||
else:
|
||||
# Unknown type
|
||||
exec_summ.append({"type": a_type or "", "status": "blocked"})
|
||||
# Feed back the execution summary to the LLM for memory/context in the next loop.
|
||||
if exec_summ:
|
||||
add_msg("user", json.dumps({"executed": exec_summ}, ensure_ascii=False))
|
||||
|
||||
# If we exhaust steps without finish, return non-zero so orchestrator can decide next round
|
||||
log("Setup steps exhausted without finish action.")
|
||||
_append_jsonl(conv_jsonl, {"ts": _now_iso(), "event": "exhausted"})
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Standard entry point of the script. Calls main() and uses its return value as the process exit code.
|
||||
sys.exit(main())
|
||||
1256
experiments/optimization-factory/scripts/run_optimization.sh
Normal file
1256
experiments/optimization-factory/scripts/run_optimization.sh
Normal file
File diff suppressed because it is too large
Load diff
1
experiments/optimization-factory/server/__init__.py
Normal file
1
experiments/optimization-factory/server/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Server package for Optimizer Factory
|
||||
1127
experiments/optimization-factory/server/analyzer.py
Normal file
1127
experiments/optimization-factory/server/analyzer.py
Normal file
File diff suppressed because it is too large
Load diff
2709
experiments/optimization-factory/server/app.py
Normal file
2709
experiments/optimization-factory/server/app.py
Normal file
File diff suppressed because it is too large
Load diff
926
experiments/optimization-factory/server/static/app.js
Normal file
926
experiments/optimization-factory/server/static/app.js
Normal file
|
|
@ -0,0 +1,926 @@
|
|||
/**
|
||||
* Generic API helper function for making HTTP requests to the Flask backend
|
||||
* @param {string} path - API endpoint path (e.g., '/api/repos')
|
||||
* @param {Object} opts - Fetch options (method, body, etc.)
|
||||
* @returns {Promise<Object>} Parsed JSON response
|
||||
* @throws {Error} If response is not ok (4xx/5xx status)
|
||||
*/
|
||||
async function api(path, opts) {
|
||||
const res = await fetch(
|
||||
path,
|
||||
Object.assign(
|
||||
{ headers: { "Content-Type": "application/json" } },
|
||||
opts || {}
|
||||
)
|
||||
);
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML table row for a repository item
|
||||
* @param {Object} item - Repository data object with repo_url, last_job_id
|
||||
* @returns {string} HTML string for table row with action buttons
|
||||
*/
|
||||
function rowHtml(item) {
|
||||
const esc = (s) => (s || "").replaceAll("<", "<").replaceAll(">", ">");
|
||||
const tierClass = `tier-${item.resource_tier}`;
|
||||
return `<tr>
|
||||
<td><div class="repo-url">${esc(item.repo_url)}</div></td>
|
||||
<td>${esc(item.module_root)}</td>
|
||||
<td>${esc(item.tests_root)}</td>
|
||||
<td><div class="job-id">${esc(item.last_job_id || "")}</div></td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-primary btn-sm" data-action="analyze" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>🧠</span> Analyze
|
||||
</button>
|
||||
<button class="btn btn-success btn-sm" data-action="run" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>▶️</span> Run
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="status" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>📊</span> Status
|
||||
</button>
|
||||
<button class="btn btn-info btn-sm" data-action="track" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>🛰️</span> Track
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="logs" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>📄</span> Logs
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="download-logs" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>⬇️</span> Download Logs
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" data-action="terminate" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>⛔</span> Terminate
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" data-action="delete" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>🗑️</span> Delete
|
||||
</button>
|
||||
<button class="btn btn-warning btn-sm" data-action="restart" data-repo="${esc(
|
||||
item.repo_url
|
||||
)}">
|
||||
<span>🔁</span> Restart
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the repository table by fetching data from the backend
|
||||
* Shows loading state during the operation
|
||||
*/
|
||||
async function refresh() {
|
||||
const container = document.querySelector(".container");
|
||||
container.classList.add("loading");
|
||||
try {
|
||||
const data = await api("/api/repos");
|
||||
const tbody = document.querySelector("#repos tbody");
|
||||
tbody.innerHTML = data.items.map(rowHtml).join("");
|
||||
} finally {
|
||||
container.classList.remove("loading");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new repository or updates an existing one
|
||||
* @param {boolean} isUpdate - If true, updates existing repo; if false, adds new repo
|
||||
* Shows loading state and clears form after successful add
|
||||
*/
|
||||
async function addOrUpdate(isUpdate) {
|
||||
const payload = {
|
||||
repo_url: document.getElementById("repo_url").value.trim(),
|
||||
};
|
||||
if (!payload.repo_url) {
|
||||
alert("Repository URL is required");
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = isUpdate
|
||||
? document.getElementById("update")
|
||||
: document.getElementById("add");
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = isUpdate
|
||||
? "<span>⏳</span> Updating..."
|
||||
: "<span>⏳</span> Adding...";
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
await api("/api/repos", {
|
||||
method: isUpdate ? "PUT" : "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
await refresh();
|
||||
// Clear form after successful add
|
||||
if (!isUpdate) {
|
||||
document.getElementById("repo_url").value = "";
|
||||
}
|
||||
} catch (e) {
|
||||
alert(`Error: ${e}`);
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs optimization for all repositories in the CSV
|
||||
* Shows loading state and refreshes the table after completion
|
||||
*/
|
||||
async function runAll() {
|
||||
const btn = document.getElementById("runAll");
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = "<span>⏳</span> Running All...";
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
await api("/api/run_all", { method: "POST", body: JSON.stringify({}) });
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
alert(`Error: ${e}`);
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs optimization for a single repository
|
||||
* @param {string} repo - Repository URL to run optimization for
|
||||
* Shows loading state on the specific button and refreshes table after completion
|
||||
*/
|
||||
async function runSingle(repo) {
|
||||
const btn = document.querySelector(
|
||||
`button[data-action="run"][data-repo="${repo}"]`
|
||||
);
|
||||
if (btn) {
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = "<span>⏳</span> Running...";
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
await api("/api/run", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ repo_url: repo }),
|
||||
});
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
alert(`Error: ${e}`);
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and displays the job status for a repository
|
||||
* @param {string} repo - Repository URL to get status for
|
||||
* Updates the status panel with job information (status, timestamps, etc.)
|
||||
*/
|
||||
async function showStatus(repo) {
|
||||
const statusEl = document.getElementById("status");
|
||||
statusEl.textContent = "Loading status...";
|
||||
|
||||
try {
|
||||
const data = await api(
|
||||
"/api/job_status?repo_url=" + encodeURIComponent(repo)
|
||||
);
|
||||
statusEl.textContent = JSON.stringify(data.job, null, 2);
|
||||
} catch (e) {
|
||||
statusEl.textContent = `Error loading status: ${e}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and displays the job logs for a repository
|
||||
* @param {string} repo - Repository URL to get logs for
|
||||
* Updates the logs panel with CloudWatch log events from the job
|
||||
*/
|
||||
async function showLogs(repo) {
|
||||
const logsEl = document.getElementById("logs");
|
||||
logsEl.textContent = "Loading logs...";
|
||||
|
||||
try {
|
||||
const data = await api(
|
||||
"/api/job_logs?repo_url=" + encodeURIComponent(repo)
|
||||
);
|
||||
logsEl.textContent = (data.events || []).join("\n");
|
||||
} catch (e) {
|
||||
logsEl.textContent = `Error loading logs: ${e}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple tracker: polls /api/job_logs and shows current stage and latest logs
|
||||
async function trackRepo(repo) {
|
||||
const logsEl = document.getElementById("logs");
|
||||
logsEl.textContent = `Tracking ${repo}...`;
|
||||
let stop = false;
|
||||
const stopAfterMs = 10 * 60 * 1000; // 10 minutes client-side tracking
|
||||
const start = Date.now();
|
||||
while (!stop) {
|
||||
try {
|
||||
const data = await api(
|
||||
"/api/job_logs?repo_url=" + encodeURIComponent(repo)
|
||||
);
|
||||
const stage = data.stage
|
||||
? `\n\nStage: ${JSON.stringify(data.stage)}`
|
||||
: "";
|
||||
logsEl.textContent = (data.events || []).join("\n") + stage;
|
||||
} catch (e) {
|
||||
logsEl.textContent = `Error loading logs: ${e}`;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
if (Date.now() - start > stopAfterMs) stop = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global click event handler for all action buttons
|
||||
* Handles run, status, logs, and delete actions for repositories
|
||||
* Shows confirmation dialog for delete operations
|
||||
*/
|
||||
document.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("button");
|
||||
if (!btn) return;
|
||||
const action = btn.getAttribute("data-action");
|
||||
const repo = btn.getAttribute("data-repo");
|
||||
|
||||
if (action === "run") runSingle(repo);
|
||||
if (action === "status") showStatus(repo);
|
||||
if (action === "logs") showLogs(repo);
|
||||
if (action === "track") trackRepo(repo);
|
||||
if (action === "restart") restartRepo(repo, btn);
|
||||
if (action === "download-logs") downloadLogs(repo);
|
||||
if (action === "analyze") startAnalysis(repo);
|
||||
if (action === "terminate") terminateRepo(repo, btn);
|
||||
if (action === "delete") {
|
||||
if (confirm(`Are you sure you want to delete "${repo}"?`)) {
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = "<span>⏳</span> Deleting...";
|
||||
btn.disabled = true;
|
||||
|
||||
api("/api/repos", {
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ repo_url: repo }),
|
||||
})
|
||||
.then(refresh)
|
||||
.catch((err) => {
|
||||
alert(`Error: ${err}`);
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function downloadLogs(repo) {
|
||||
try {
|
||||
const btn = document.querySelector(
|
||||
`button[data-action="download-logs"][data-repo="${repo}"]`
|
||||
);
|
||||
const originalText = btn ? btn.innerHTML : null;
|
||||
if (btn) {
|
||||
btn.innerHTML = "<span>⬇️</span> Preparing...";
|
||||
btn.disabled = true;
|
||||
}
|
||||
|
||||
// Download all logs as a zip with proper naming from server
|
||||
const res = await fetch(
|
||||
"/api/job_logs/download_all?repo_url=" + encodeURIComponent(repo)
|
||||
);
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
// Stream to blob with progress (requires Content-Length from server)
|
||||
const reader = res.body.getReader();
|
||||
const chunks = [];
|
||||
let received = 0;
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
received += value.length;
|
||||
if (btn) {
|
||||
const total = Number(res.headers.get("Content-Length") || 0);
|
||||
if (total > 0) {
|
||||
const pct = Math.min(100, Math.floor((received / total) * 100));
|
||||
btn.innerHTML = `<span>⬇️</span> ${pct}% (${Math.ceil(
|
||||
received / 1024 / 1024
|
||||
)} MB / ${Math.ceil(total / 1024 / 1024)} MB)`;
|
||||
} else {
|
||||
btn.innerHTML = `<span>⬇️</span> ${Math.ceil(
|
||||
received / 1024 / 1024
|
||||
)} MB`;
|
||||
}
|
||||
}
|
||||
}
|
||||
const blob = new Blob(chunks, { type: "application/zip" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
// Let browser use server-provided filename from Content-Disposition
|
||||
const cd = res.headers.get("Content-Disposition") || "";
|
||||
const m = cd.match(/filename=([^;]+)/);
|
||||
a.download = m ? m[1] : "logs.zip";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
if (btn && originalText) {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
alert(`Download error: ${e}`);
|
||||
const btn = document.querySelector(
|
||||
`button[data-action="download-logs"][data-repo="${repo}"]`
|
||||
);
|
||||
if (btn) {
|
||||
btn.innerHTML = "<span>⚠️</span> Retry";
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function terminateRepo(repo, btn) {
|
||||
if (!confirm(`Terminate EC2 job for ${repo}?`)) return;
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = "<span>⏳</span> Terminating...";
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await api("/api/terminate", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ repo_url: repo }),
|
||||
});
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
alert(`Error: ${e}`);
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function restartRepo(repo, btn) {
|
||||
if (!confirm(`Restart optimization for ${repo}?`)) return;
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = "<span>⏳</span> Restarting...";
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await api("/api/restart", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ repo_url: repo }),
|
||||
});
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
alert(`Error: ${e}`);
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners for main action buttons
|
||||
document.getElementById("refresh").addEventListener("click", refresh);
|
||||
document.getElementById("runAll").addEventListener("click", runAll);
|
||||
document
|
||||
.getElementById("add")
|
||||
.addEventListener("click", () => addOrUpdate(false));
|
||||
document
|
||||
.getElementById("update")
|
||||
.addEventListener("click", () => addOrUpdate(true));
|
||||
|
||||
async function startAnalysis(repo, opts) {
|
||||
const silent = opts && opts.silent;
|
||||
|
||||
// Show loading state on the analyze button
|
||||
const btn = document.querySelector(
|
||||
`button[data-action="analyze"][data-repo="${repo}"]`
|
||||
);
|
||||
let originalText = null;
|
||||
if (btn && !silent) {
|
||||
originalText = btn.innerHTML;
|
||||
btn.innerHTML = "<span>⏳</span> Analyzing...";
|
||||
btn.disabled = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await api("/api/analyze_repo", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ repo_url: repo }),
|
||||
});
|
||||
|
||||
if (!silent) {
|
||||
await pollAnalysisAndAutoApply(res.analysis_id, repo);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!silent) {
|
||||
alert(`Error starting analysis: ${e}`);
|
||||
}
|
||||
} finally {
|
||||
// Restore button state
|
||||
if (btn && originalText && !silent) {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function pollAnalysisAndAutoApply(analysisId, repo) {
|
||||
console.log(
|
||||
`[Analysis] Starting analysis polling for repo: ${repo}, analysisId: ${analysisId}`
|
||||
);
|
||||
|
||||
let status = "queued";
|
||||
for (let i = 0; i < 60; i++) {
|
||||
try {
|
||||
const st = await api(
|
||||
`/api/analyze_repo/status?analysis_id=${encodeURIComponent(analysisId)}`
|
||||
);
|
||||
status = st.status;
|
||||
console.log(
|
||||
`[Analysis] Poll ${i + 1}: Status is ${status} for repo ${repo}`
|
||||
);
|
||||
|
||||
if (status === "succeeded") break;
|
||||
if (status === "failed") {
|
||||
console.error(
|
||||
`[Analysis] Analysis failed for repo ${repo}: ${st.message}`
|
||||
);
|
||||
alert(
|
||||
`❌ Analysis failed for ${repo}: ${st.message || "Unknown error"}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
} catch (e) {
|
||||
console.error(`[Analysis] Error polling status for repo ${repo}:`, e);
|
||||
alert(`Error polling analysis status: ${e}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (status !== "succeeded") {
|
||||
console.warn(
|
||||
`[Analysis] Timeout waiting for analysis result for repo ${repo}`
|
||||
);
|
||||
alert(
|
||||
`⏱️ Timeout waiting for analysis result for ${repo}. Please try again.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the analysis results
|
||||
const res = await api(
|
||||
`/api/analyze_repo/result?analysis_id=${encodeURIComponent(analysisId)}`
|
||||
);
|
||||
const result = res.result || {};
|
||||
console.log(`[Analysis] Got analysis results for repo ${repo}:`, result);
|
||||
|
||||
// Automatically apply all analysis results
|
||||
const apply = {
|
||||
module_root: true,
|
||||
tests_root: true,
|
||||
resource_tier: true,
|
||||
};
|
||||
|
||||
await api("/api/apply_analysis", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ repo_url: repo, apply }),
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[Analysis] Successfully applied analysis results for repo ${repo}`
|
||||
);
|
||||
|
||||
// Show success message with analysis details
|
||||
const cf = result.codeflash || {};
|
||||
const resources = result.resources || {};
|
||||
const tests = result.tests || {};
|
||||
|
||||
const successMessage = `✅ Analysis completed and applied successfully for ${repo}!
|
||||
|
||||
Analysis Results:
|
||||
• Package Manager: ${result.package_manager || "unknown"}
|
||||
• Confidence: ${(result.confidence ?? 0).toFixed(2)}
|
||||
• Module Root: ${cf.module_root || "auto"}
|
||||
• Tests Root: ${cf.tests_root || "auto"}
|
||||
• Test Command: ${tests.test_command || "pytest"}
|
||||
|
||||
All settings have been automatically saved to the CSV configuration.`;
|
||||
|
||||
alert(successMessage);
|
||||
|
||||
// Refresh the table to show updated data
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[Analysis] Error applying analysis results for repo ${repo}:`,
|
||||
e
|
||||
);
|
||||
alert(`Error applying analysis results: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function pollAnalysisAndShow(analysisId, repo) {
|
||||
const modal = document.getElementById("analysisModal");
|
||||
const content = document.getElementById("analysisContent");
|
||||
modal.style.display = "flex";
|
||||
content.innerHTML = `<p>Analyzing <strong>${repo}</strong>... please wait.</p>`;
|
||||
document.getElementById("applyAnalysis").disabled = true;
|
||||
|
||||
let status = "queued";
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const st = await api(
|
||||
`/api/analyze_repo/status?analysis_id=${encodeURIComponent(analysisId)}`
|
||||
);
|
||||
status = st.status;
|
||||
if (status === "succeeded") break;
|
||||
if (status === "failed") {
|
||||
content.innerHTML = `<p>❌ Analysis failed: ${
|
||||
st.message || "Unknown error"
|
||||
}</p>`;
|
||||
return;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
}
|
||||
|
||||
if (status !== "succeeded") {
|
||||
content.innerHTML = `<p>Timeout waiting for analysis result.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await api(
|
||||
`/api/analyze_repo/result?analysis_id=${encodeURIComponent(analysisId)}`
|
||||
);
|
||||
const result = res.result || {};
|
||||
|
||||
const cf = result.codeflash || {};
|
||||
const resources = result.resources || {};
|
||||
const tests = result.tests || {};
|
||||
|
||||
content.innerHTML = `
|
||||
<div>
|
||||
<p><strong>Repo:</strong> ${repo}</p>
|
||||
<p><strong>Package Manager:</strong> ${
|
||||
result.package_manager || "unknown"
|
||||
}</p>
|
||||
<p><strong>Confidence:</strong> ${(result.confidence ?? 0).toFixed(2)}</p>
|
||||
<hr/>
|
||||
<h4>Proposed Codeflash Config</h4>
|
||||
<ul>
|
||||
<li>module_root: <code>${cf.module_root || "auto"}</code></li>
|
||||
<li>tests_root: <code>${cf.tests_root || "auto"}</code></li>
|
||||
<li>resource_tier: <code>${resources.tier || "small"}</code></li>
|
||||
<li>test_command: <code>${tests.test_command || "pytest"}</code></li>
|
||||
</ul>
|
||||
<div style="margin-top:8px;">
|
||||
<label><input type="checkbox" id="apply_module_root" checked> Apply module_root</label>
|
||||
<label style="margin-left:12px;"><input type="checkbox" id="apply_tests_root" checked> Apply tests_root</label>
|
||||
<label style="margin-left:12px;"><input type="checkbox" id="apply_resource_tier" checked> Apply resource_tier</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById("applyAnalysis").disabled = false;
|
||||
document.getElementById("applyAnalysis").onclick = async () => {
|
||||
const apply = {
|
||||
module_root: document.getElementById("apply_module_root").checked,
|
||||
tests_root: document.getElementById("apply_tests_root").checked,
|
||||
resource_tier: document.getElementById("apply_resource_tier").checked,
|
||||
};
|
||||
try {
|
||||
await api("/api/apply_analysis", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ repo_url: repo, apply }),
|
||||
});
|
||||
alert("Applied to CSV");
|
||||
modal.style.display = "none";
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
alert(`Error applying analysis: ${e}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById("closeAnalysisModal").addEventListener("click", () => {
|
||||
document.getElementById("analysisModal").style.display = "none";
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// BULK UPLOAD FUNCTIONALITY
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Handles tab switching between single and bulk upload forms
|
||||
* @param {string} tabName - Either 'single' or 'bulk'
|
||||
*/
|
||||
function switchTab(tabName) {
|
||||
// Update tab buttons
|
||||
document
|
||||
.getElementById("singleTab")
|
||||
.classList.toggle("active", tabName === "single");
|
||||
document
|
||||
.getElementById("bulkTab")
|
||||
.classList.toggle("active", tabName === "bulk");
|
||||
|
||||
// Update tab content
|
||||
document
|
||||
.getElementById("singleForm")
|
||||
.classList.toggle("active", tabName === "single");
|
||||
document
|
||||
.getElementById("bulkForm")
|
||||
.classList.toggle("active", tabName === "bulk");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles CSV file selection and preview
|
||||
*/
|
||||
function handleCsvFileSelection() {
|
||||
const fileInput = document.getElementById("csvFile");
|
||||
const fileWrapper = fileInput.parentElement;
|
||||
const fileText = fileWrapper.querySelector(".file-input-text");
|
||||
const validateBtn = document.getElementById("validateCsv");
|
||||
const uploadBtn = document.getElementById("uploadCsv");
|
||||
const csvPreview = document.getElementById("csvPreview");
|
||||
const validationResults = document.getElementById("validationResults");
|
||||
|
||||
if (fileInput.files.length > 0) {
|
||||
const file = fileInput.files[0];
|
||||
fileWrapper.classList.add("has-file");
|
||||
fileText.textContent = `Selected: ${file.name}`;
|
||||
validateBtn.disabled = false;
|
||||
|
||||
// Read and preview file
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
const content = e.target.result;
|
||||
document.getElementById("csvContent").textContent =
|
||||
content.substring(0, 1000) +
|
||||
(content.length > 1000 ? "\n... (truncated)" : "");
|
||||
csvPreview.style.display = "block";
|
||||
|
||||
// Store CSV content for validation
|
||||
fileInput.csvContent = content;
|
||||
};
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
fileWrapper.classList.remove("has-file");
|
||||
fileText.textContent = "Choose CSV file...";
|
||||
validateBtn.disabled = true;
|
||||
uploadBtn.disabled = true;
|
||||
csvPreview.style.display = "none";
|
||||
validationResults.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates CSV content by calling the backend
|
||||
*/
|
||||
async function validateCsv() {
|
||||
const fileInput = document.getElementById("csvFile");
|
||||
const validateBtn = document.getElementById("validateCsv");
|
||||
const uploadBtn = document.getElementById("uploadCsv");
|
||||
const validationResults = document.getElementById("validationResults");
|
||||
|
||||
if (!fileInput.csvContent) {
|
||||
alert("Please select a CSV file first");
|
||||
return;
|
||||
}
|
||||
|
||||
const originalText = validateBtn.innerHTML;
|
||||
validateBtn.innerHTML = "<span>⏳</span> Validating...";
|
||||
validateBtn.disabled = true;
|
||||
uploadBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await api("/api/repos/bulk", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ csv_data: fileInput.csvContent }),
|
||||
});
|
||||
|
||||
// Display validation results
|
||||
displayValidationResults(response);
|
||||
validationResults.style.display = "block";
|
||||
|
||||
// Enable upload button if validation passed
|
||||
if (response.ok) {
|
||||
uploadBtn.disabled = false;
|
||||
uploadBtn.innerHTML = "<span>📤</span> Upload Repositories";
|
||||
} else {
|
||||
uploadBtn.disabled = true;
|
||||
uploadBtn.innerHTML = "<span>📤</span> Fix Errors First";
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Validation error: ${err}`);
|
||||
validationResults.style.display = "none";
|
||||
} finally {
|
||||
validateBtn.innerHTML = originalText;
|
||||
validateBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays validation results in the UI
|
||||
* @param {Object} response - Response from validation API
|
||||
*/
|
||||
function displayValidationResults(response) {
|
||||
const summaryEl = document.getElementById("validationSummary");
|
||||
const detailsEl = document.getElementById("validationDetails");
|
||||
|
||||
// Display summary stats
|
||||
const stats = response.stats || {};
|
||||
summaryEl.innerHTML = `
|
||||
<div class="validation-stat total">
|
||||
<span class="validation-stat-number">${stats.total_rows || 0}</span>
|
||||
Total Rows
|
||||
</div>
|
||||
<div class="validation-stat valid">
|
||||
<span class="validation-stat-number">${stats.valid_count || 0}</span>
|
||||
Valid
|
||||
</div>
|
||||
<div class="validation-stat warnings">
|
||||
<span class="validation-stat-number">${stats.warning_count || 0}</span>
|
||||
Warnings
|
||||
</div>
|
||||
<div class="validation-stat errors">
|
||||
<span class="validation-stat-number">${stats.error_count || 0}</span>
|
||||
Errors
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Display detailed validation results
|
||||
const results = response.validation_results || [];
|
||||
if (results.length > 0) {
|
||||
detailsEl.innerHTML = results
|
||||
.map((result) => {
|
||||
const hasErrors = result.errors && result.errors.length > 0;
|
||||
const hasWarnings = result.warnings && result.warnings.length > 0;
|
||||
const cssClass = hasErrors
|
||||
? "has-errors"
|
||||
: hasWarnings
|
||||
? "has-warnings"
|
||||
: "valid";
|
||||
|
||||
let messagesHtml = "";
|
||||
if (hasErrors || hasWarnings) {
|
||||
const messages = [
|
||||
...(result.errors || []).map(
|
||||
(msg) => `<li class="error">${msg}</li>`
|
||||
),
|
||||
...(result.warnings || []).map(
|
||||
(msg) => `<li class="warning">${msg}</li>`
|
||||
),
|
||||
];
|
||||
messagesHtml = `<ul class="validation-messages">${messages.join(
|
||||
""
|
||||
)}</ul>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="validation-row ${cssClass}">
|
||||
<div class="validation-row-header">
|
||||
Line ${result.line}: ${result.repo_url || "(no URL)"}
|
||||
</div>
|
||||
${messagesHtml}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
} else {
|
||||
detailsEl.innerHTML = "<p>No validation details available.</p>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads validated CSV data to create repositories
|
||||
*/
|
||||
async function uploadCsv() {
|
||||
const fileInput = document.getElementById("csvFile");
|
||||
const uploadBtn = document.getElementById("uploadCsv");
|
||||
|
||||
if (!fileInput.csvContent) {
|
||||
alert("Please select and validate a CSV file first");
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!confirm("This will add all valid repositories from the CSV. Continue?")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originalText = uploadBtn.innerHTML;
|
||||
uploadBtn.innerHTML = "<span>⏳</span> Uploading...";
|
||||
uploadBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await api("/api/repos/bulk", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ csv_data: fileInput.csvContent }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert(response.message || "Repositories uploaded successfully!");
|
||||
|
||||
// Clear form and refresh table
|
||||
document.getElementById("csvFile").value = "";
|
||||
handleCsvFileSelection();
|
||||
refresh();
|
||||
|
||||
// Switch back to single form
|
||||
switchTab("single");
|
||||
} else {
|
||||
alert(`Upload failed: ${response.error || "Unknown error"}`);
|
||||
// Re-display validation results
|
||||
displayValidationResults(response);
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Upload error: ${err}`);
|
||||
} finally {
|
||||
uploadBtn.innerHTML = originalText;
|
||||
uploadBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a CSV template file
|
||||
*/
|
||||
function downloadCsvTemplate() {
|
||||
const csvContent = `repo_url,module_root,tests_root,resource_tier
|
||||
https://github.com/psf/requests,requests,tests,small
|
||||
https://github.com/pallets/flask,src/flask,tests,medium
|
||||
https://github.com/your-org/your-repo,auto,auto,small`;
|
||||
|
||||
const blob = new Blob([csvContent], { type: "text/csv" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "repos_template.csv";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EVENT LISTENERS
|
||||
// =============================================================================
|
||||
|
||||
// Tab switching
|
||||
document
|
||||
.getElementById("singleTab")
|
||||
.addEventListener("click", () => switchTab("single"));
|
||||
document
|
||||
.getElementById("bulkTab")
|
||||
.addEventListener("click", () => switchTab("bulk"));
|
||||
|
||||
// File input handling
|
||||
document
|
||||
.getElementById("csvFile")
|
||||
.addEventListener("change", handleCsvFileSelection);
|
||||
document
|
||||
.querySelector(".file-input-wrapper")
|
||||
.addEventListener("click", function () {
|
||||
document.getElementById("csvFile").click();
|
||||
});
|
||||
|
||||
// Bulk upload actions
|
||||
document.getElementById("validateCsv").addEventListener("click", validateCsv);
|
||||
document.getElementById("uploadCsv").addEventListener("click", uploadCsv);
|
||||
document
|
||||
.getElementById("downloadTemplate")
|
||||
.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
downloadCsvTemplate();
|
||||
});
|
||||
|
||||
// Event listeners for main action buttons
|
||||
document.getElementById("refresh").addEventListener("click", refresh);
|
||||
document.getElementById("runAll").addEventListener("click", runAll);
|
||||
document
|
||||
.getElementById("add")
|
||||
.addEventListener("click", () => addOrUpdate(false));
|
||||
document
|
||||
.getElementById("update")
|
||||
.addEventListener("click", () => addOrUpdate(true));
|
||||
|
||||
// Initialize the page by loading repository data
|
||||
refresh();
|
||||
142
experiments/optimization-factory/server/static/index.html
Normal file
142
experiments/optimization-factory/server/static/index.html
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Optimizer Factory</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Optimizer Factory</h1>
|
||||
<p>Manage Codeflash optimizations across Python repositories using EC2</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="actions">
|
||||
<button id="refresh" class="btn btn-secondary">
|
||||
<span>🔄</span> Refresh
|
||||
</button>
|
||||
<button id="runAll" class="btn btn-success">
|
||||
<span>🚀</span> Run All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h2>Add / Update Repository</h2>
|
||||
<div class="form-tabs">
|
||||
<button id="singleTab" class="tab-btn active">Single Repository</button>
|
||||
<button id="bulkTab" class="tab-btn">Bulk Upload (CSV)</button>
|
||||
</div>
|
||||
|
||||
<div id="singleForm" class="tab-content active">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label for="repo_url">Repository URL</label>
|
||||
<input id="repo_url" placeholder="https://github.com/org/repo" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button id="add" class="btn btn-primary">
|
||||
<span>➕</span> Add Repository
|
||||
</button>
|
||||
<button id="update" class="btn btn-secondary">
|
||||
<span>✏️</span> Update Repository
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bulkForm" class="tab-content">
|
||||
<div class="bulk-upload-section">
|
||||
<div class="form-group">
|
||||
<label for="csvFile">CSV File</label>
|
||||
<div class="file-input-wrapper">
|
||||
<input type="file" id="csvFile" accept=".csv" />
|
||||
<span class="file-input-text">Choose CSV file...</span>
|
||||
</div>
|
||||
<div class="help-text">
|
||||
CSV format: repo_url
|
||||
<br>
|
||||
<a href="#" id="downloadTemplate">Download template</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="csv-preview" id="csvPreview" style="display: none;">
|
||||
<h4>CSV Preview</h4>
|
||||
<div class="csv-content" id="csvContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="validation-results" id="validationResults" style="display: none;">
|
||||
<h4>Validation Results</h4>
|
||||
<div class="validation-summary" id="validationSummary"></div>
|
||||
<div class="validation-details" id="validationDetails"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button id="validateCsv" class="btn btn-secondary" disabled>
|
||||
<span>🔍</span> Validate CSV
|
||||
</button>
|
||||
<button id="uploadCsv" class="btn btn-success" disabled>
|
||||
<span>📤</span> Upload Repositories
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-section">
|
||||
<h2>Repositories</h2>
|
||||
<div class="table-wrapper">
|
||||
<table id="repos">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Repository</th>
|
||||
<th>Module Root</th>
|
||||
<th>Tests Root</th>
|
||||
<th>Last EC2 ID</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs-section">
|
||||
<h2>Job Status & Logs</h2>
|
||||
<div class="logs-container">
|
||||
<div class="log-panel">
|
||||
<h3>Job Status</h3>
|
||||
<pre id="status" class="log-content"></pre>
|
||||
</div>
|
||||
<div class="log-panel">
|
||||
<h3>Job Logs</h3>
|
||||
<pre id="logs" class="log-content"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analysis Modal -->
|
||||
<div id="analysisModal"
|
||||
style="display:none; position:fixed; inset:0; background: rgba(0,0,0,0.4); align-items:center; justify-content:center;">
|
||||
<div style="background:#fff; max-width:900px; width:95%; border-radius:10px; padding:16px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<h3>LLM Analysis</h3>
|
||||
<button id="closeAnalysisModal" class="btn btn-secondary btn-sm">Close</button>
|
||||
</div>
|
||||
<div id="analysisContent" style="margin-top:10px; max-height:60vh; overflow:auto;"></div>
|
||||
<div class="form-actions" style="margin-top:12px;">
|
||||
<button id="applyAnalysis" class="btn btn-primary"><span>💾</span> Apply to CSV</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
640
experiments/optimization-factory/server/static/style.css
Normal file
640
experiments/optimization-factory/server/static/style.css
Normal file
|
|
@ -0,0 +1,640 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
||||
color: white;
|
||||
padding: 24px 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header p {
|
||||
margin: 8px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #4f46e5;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #4338ca;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #059669;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #dc2626;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6b7280;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #4b5563;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(107, 114, 128, 0.3);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
margin: 0 0 20px 0;
|
||||
color: #1e293b;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Tab styling */
|
||||
.form-tabs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 8px 8px 0 0;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: #3b82f6;
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: #3b82f6;
|
||||
border-bottom-color: #3b82f6;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #4f46e5;
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.table-section h2 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #1e293b;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
font-weight: 600;
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
font-size: 14px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.repo-url {
|
||||
color: #4f46e5;
|
||||
font-weight: 500;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tier-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tier-small {
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.tier-medium {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.tier-large {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.job-id {
|
||||
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.action-buttons .btn-sm {
|
||||
padding: 6px 10px;
|
||||
font-size: 11px;
|
||||
min-width: 70px;
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-buttons .btn-sm span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.logs-section {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.logs-section h2 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #1e293b;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.log-panel {
|
||||
background: white;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-panel h3 {
|
||||
background: #f8fafc;
|
||||
margin: 0;
|
||||
padding: 16px 20px;
|
||||
color: #374151;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 20px;
|
||||
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.log-content:empty:after {
|
||||
content: "No data available. Click Status or Logs buttons to load information.";
|
||||
color: #64748b;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Bulk upload styling */
|
||||
.bulk-upload-section {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.file-input-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border: 2px dashed #cbd5e0;
|
||||
border-radius: 8px;
|
||||
background: #f7fafc;
|
||||
transition: all 0.3s ease;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.file-input-wrapper input[type="file"] {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
.file-input-wrapper:hover {
|
||||
border-color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.file-input-wrapper.has-file {
|
||||
border-color: #10b981;
|
||||
background: #f0fdf4;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.file-input-text {
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-input-wrapper.has-file .file-input-text {
|
||||
color: #059669;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.help-text a {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.help-text a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.csv-preview,
|
||||
.validation-results {
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.csv-preview h4,
|
||||
.validation-results h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: #1e293b;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.csv-content {
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
background: #f8fafc;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.validation-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.validation-stat {
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.validation-stat.total {
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.validation-stat.valid {
|
||||
background: #f0fdf4;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.validation-stat.warnings {
|
||||
background: #fffbeb;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.validation-stat.errors {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.validation-stat-number {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.validation-details {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.validation-row {
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.validation-row.has-errors {
|
||||
background: #fef2f2;
|
||||
border-left-color: #dc2626;
|
||||
}
|
||||
|
||||
.validation-row.has-warnings {
|
||||
background: #fffbeb;
|
||||
border-left-color: #d97706;
|
||||
}
|
||||
|
||||
.validation-row.valid {
|
||||
background: #f0fdf4;
|
||||
border-left-color: #059669;
|
||||
}
|
||||
|
||||
.validation-row-header {
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.validation-messages {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.validation-messages li {
|
||||
padding: 4px 0;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.validation-messages .error {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.validation-messages .warning {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.validation-messages .error::before {
|
||||
content: "❌";
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.validation-messages .warning::before {
|
||||
content: "⚠️";
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
margin: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-tabs {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
border-bottom-color: #3b82f6;
|
||||
}
|
||||
|
||||
.validation-summary {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.csv-content {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
9
experiments/optimization-factory/tools/requirements.txt
Normal file
9
experiments/optimization-factory/tools/requirements.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
boto3>=1.34.0
|
||||
Flask>=3.0.0
|
||||
python-dotenv>=1.0.0
|
||||
anthropic>=0.31.0
|
||||
jsonschema>=4.21.1
|
||||
requests>=2.32.0
|
||||
paramiko>=3.4.0
|
||||
|
||||
|
||||
22
js/VSC-Extension/package-lock.json
generated
22
js/VSC-Extension/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "codeflash",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeflash",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.11",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
"@codeflash/shared": "*",
|
||||
"@codeflash/types": "*",
|
||||
"@vscode/python-extension": "^1.0.5",
|
||||
"diff": "^8.0.2",
|
||||
"marked": "^15.0.12",
|
||||
"p-queue": "^8.1.0",
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
|
|
@ -4257,10 +4258,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
|
||||
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
|
||||
"dev": true,
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz",
|
||||
"integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
|
|
@ -8429,6 +8429,16 @@
|
|||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/diff": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
|
||||
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/glob": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "codeflash",
|
||||
"displayName": "Codeflash",
|
||||
"description": "Optimize Your Python Code - Automatically",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.11",
|
||||
"icon": "media/Codeflash_black_background.jpg",
|
||||
"publisher": "codeflash",
|
||||
"repository": {
|
||||
|
|
@ -144,6 +144,7 @@
|
|||
"@codeflash/shared": "*",
|
||||
"@codeflash/types": "*",
|
||||
"@vscode/python-extension": "^1.0.5",
|
||||
"diff": "^8.0.2",
|
||||
"marked": "^15.0.12",
|
||||
"p-queue": "^8.1.0",
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { useStore } from "./store/root";
|
|||
import ApiKeyForm from "./components/apiKeyError";
|
||||
import { messageHandler } from "./utils/webviewMessageHandler";
|
||||
// import OptimizeCurrentDiff from "./components/optimizeCurrentDiff";
|
||||
import { vscode } from "./utils/vscode";
|
||||
// import CurrentFileFunctions from "./components/currentFileFunctions";
|
||||
import { vscode } from "./utils/vscode";
|
||||
import ChatView from "./components/chatView";
|
||||
import Tabs from "./components/tabs";
|
||||
import OptimizationQueue from "./components/optimizationQueue";
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const ChatViewBanner = () => {
|
|||
<div className={styles.logoContainer}>
|
||||
<CodeflashLogo width="60%" height="100%" />
|
||||
</div>
|
||||
<p className={styles.description}>Optimize your python code with AI.</p>
|
||||
<p className={styles.description}>Optimize your Python code with AI.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ const TaskAction = ({ task }: { task: TQueueTaskItem }) => {
|
|||
</button>
|
||||
)}
|
||||
|
||||
{task.patch_file && (
|
||||
{task.patchFile && (
|
||||
<button
|
||||
className={styles.queueTaskPatch}
|
||||
onClick={() => {
|
||||
|
|
@ -273,8 +273,7 @@ const TaskAction = ({ task }: { task: TQueueTaskItem }) => {
|
|||
payload: {
|
||||
id: task.id,
|
||||
functionName: task.functionName,
|
||||
patchFile: task.patch_file!,
|
||||
patchId: task.patch_id!,
|
||||
patchFile: task.patchFile!,
|
||||
explanation: task.explanation || "",
|
||||
speedupStr: task.speedupStr || "",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ const LogItem = memo(
|
|||
<div className={styles.logEntry}>
|
||||
<div className={contentClassName} style={footerContainerStyles}>
|
||||
<MemoMarkdownBlock
|
||||
markdown={`**No optimizations found.**\n${errorLog.text}`}
|
||||
markdown={`**We couldn't find a useful optimization for this function.**\n${errorLog.text}`}
|
||||
/>
|
||||
<button
|
||||
className={styles.ghostActionButton}
|
||||
|
|
@ -175,8 +175,8 @@ const TaskLogging = ({ task }: { task: QueueTaskItem }) => {
|
|||
// initialTopMostItemIndex={task.logs.length - 1}
|
||||
totalCount={task.logs.length}
|
||||
overscan={{
|
||||
main: 1000,
|
||||
reverse: 1000,
|
||||
main: 2000,
|
||||
reverse: 1500,
|
||||
}}
|
||||
// atBottomStateChange={atBottomStateChange} // TODO: show scroll to bottom button
|
||||
followOutput={followOutput}
|
||||
|
|
@ -207,8 +207,7 @@ const TaskLogging = ({ task }: { task: QueueTaskItem }) => {
|
|||
payload: {
|
||||
id: task.id,
|
||||
functionName: task.functionName,
|
||||
patchFile: task.patch_file!,
|
||||
patchId: task.patch_id!,
|
||||
patchFile: task.patchFile!,
|
||||
explanation: task.explanation || "",
|
||||
speedupStr: task.speedupStr || "",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ export type QueueTaskItem = {
|
|||
filepath?: string;
|
||||
description?: string;
|
||||
error?: string;
|
||||
patch_file?: string;
|
||||
patch_id?: string;
|
||||
patchFile?: string;
|
||||
explanation?: string;
|
||||
speedupStr?: string;
|
||||
logs: LogEntry[];
|
||||
|
|
@ -214,7 +213,6 @@ export interface ViewPatchMessage extends WebviewMessage {
|
|||
patchFile: string;
|
||||
functionName: string;
|
||||
speedupStr?: string;
|
||||
patchId: string;
|
||||
explanation?: string;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
66
js/VSC-Extension/src/boot/baseStep.ts
Normal file
66
js/VSC-Extension/src/boot/baseStep.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { Disposable } from "../utils/dispose";
|
||||
|
||||
export class StepError extends Error {
|
||||
private _details?: string;
|
||||
private _helperCmdText?: string;
|
||||
private _helperCmd?: string;
|
||||
private _vscodeActionCommand?: {
|
||||
title: string;
|
||||
command: string;
|
||||
btnText: string;
|
||||
};
|
||||
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "StepError";
|
||||
}
|
||||
|
||||
set details(value: string) {
|
||||
this._details = value;
|
||||
}
|
||||
set vscodeActionCommand(value: {
|
||||
title: string;
|
||||
command: string;
|
||||
btnText: string;
|
||||
}) {
|
||||
this._vscodeActionCommand = value;
|
||||
}
|
||||
set helperCmd(value: string) {
|
||||
this._helperCmd = value;
|
||||
}
|
||||
set helperCmdText(value: string) {
|
||||
this._helperCmdText = value;
|
||||
}
|
||||
|
||||
get details(): string {
|
||||
return this._details!;
|
||||
}
|
||||
get helperCmdText(): string | undefined {
|
||||
return this._helperCmdText;
|
||||
}
|
||||
get helperCmd(): string | undefined {
|
||||
return this._helperCmd;
|
||||
}
|
||||
get vscodeActionCommand():
|
||||
| { title: string; command: string; btnText: string }
|
||||
| undefined {
|
||||
return this._vscodeActionCommand;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseStep<T> extends Disposable {
|
||||
private title: string = "";
|
||||
|
||||
get stepTitle(): string {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
constructor(title: string) {
|
||||
super();
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
abstract run(...args: unknown[]): Promise<StepError | T>;
|
||||
}
|
||||
|
||||
export default BaseStep;
|
||||
217
js/VSC-Extension/src/boot/bootCodeflashServer.ts
Normal file
217
js/VSC-Extension/src/boot/bootCodeflashServer.ts
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
import type { LogEntry, ViewPatchMessage } from "@codeflash/types";
|
||||
import {
|
||||
AnalysisService,
|
||||
LspService,
|
||||
NavigationService,
|
||||
OptimizationService,
|
||||
} from "../services";
|
||||
import { Logger } from "../utils";
|
||||
import { StepError } from "./baseStep";
|
||||
import BaseStep from "./baseStep";
|
||||
import * as vscode from "vscode";
|
||||
import { SidebarProvider } from "../providers/SidebarProvider";
|
||||
import { CodeflashCodeLensProvider } from "../providers/CodeLensProvider";
|
||||
import { GlobalStateKey, type GlobalState } from "../globalState";
|
||||
import type { LanguageClient } from "vscode-languageclient/node";
|
||||
import { isTaskRunning } from "@codeflash/shared";
|
||||
import type { OutgoingWebviewMessage } from "../types";
|
||||
|
||||
export type BootCodeflashServerStepResult = {
|
||||
lspService: LspService;
|
||||
optimizationLspService: LspService;
|
||||
analysisService: AnalysisService;
|
||||
optimizationService: OptimizationService;
|
||||
navigationService: NavigationService;
|
||||
sidebarProvider: SidebarProvider;
|
||||
codeLensProvider: CodeflashCodeLensProvider;
|
||||
};
|
||||
|
||||
export class BootCodeflashServerStep extends BaseStep<BootCodeflashServerStepResult> {
|
||||
logger = new Logger("BootCodeflashServerStep");
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext,
|
||||
private readonly globalState: GlobalState,
|
||||
private readonly pythonPath: string,
|
||||
private readonly optimizationEventEmitter: vscode.EventEmitter<LogEntry>,
|
||||
) {
|
||||
super("Starting Codeflash server");
|
||||
}
|
||||
|
||||
override async run(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
..._args: unknown[]
|
||||
): Promise<BootCodeflashServerStepResult | StepError> {
|
||||
try {
|
||||
const { lspService, optimizationLspService } =
|
||||
await this.provideLspServices();
|
||||
const lspMainClient = lspService.getClient();
|
||||
const optimizationClient = optimizationLspService.getClient();
|
||||
if (!lspMainClient || !optimizationClient) {
|
||||
return new StepError("LSP client is not initialized");
|
||||
}
|
||||
const services = this.provideOtherServices(
|
||||
this.context,
|
||||
this.globalState,
|
||||
lspMainClient,
|
||||
optimizationClient,
|
||||
this.optimizationEventEmitter,
|
||||
);
|
||||
this.registerGlobalLogsListener();
|
||||
return {
|
||||
lspService,
|
||||
optimizationLspService,
|
||||
...services,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to start Codeflash server: ${error}`);
|
||||
return new StepError(`Failed to start Codeflash server: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private provideLspServices = async (): Promise<{
|
||||
lspService: LspService;
|
||||
optimizationLspService: LspService;
|
||||
}> => {
|
||||
var startTime = performance.now();
|
||||
const lspService = new LspService("Main");
|
||||
|
||||
// create another client for optimization as we are using a single pipe per client, so using a single client would be
|
||||
// mean waiting for the optimization to be finished before calling any other lsp features
|
||||
const optimizationLspService = new LspService(
|
||||
"Optimization",
|
||||
this.optimizationEventEmitter,
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
lspService.start(this.pythonPath, false),
|
||||
optimizationLspService.start(this.pythonPath, true),
|
||||
]);
|
||||
|
||||
this.logger.debug("LSP client is ready and running.");
|
||||
|
||||
var endTime = performance.now();
|
||||
this.logger.info(
|
||||
`Starting codeflash server took ${endTime - startTime} ms`,
|
||||
);
|
||||
|
||||
this._disposables.push(lspService, optimizationLspService);
|
||||
|
||||
return { lspService, optimizationLspService };
|
||||
};
|
||||
|
||||
private provideOtherServices = (
|
||||
context: vscode.ExtensionContext,
|
||||
globalState: GlobalState,
|
||||
lspClient: LanguageClient,
|
||||
optimizationClient: LanguageClient,
|
||||
optimizationEventEmitter: vscode.EventEmitter<LogEntry>,
|
||||
): {
|
||||
analysisService: AnalysisService;
|
||||
optimizationService: OptimizationService;
|
||||
navigationService: NavigationService;
|
||||
sidebarProvider: SidebarProvider;
|
||||
codeLensProvider: CodeflashCodeLensProvider;
|
||||
} => {
|
||||
const analysisService = new AnalysisService(lspClient);
|
||||
const optimizationService = new OptimizationService(optimizationClient);
|
||||
const navigationService = new NavigationService();
|
||||
|
||||
const sidebarProvider = new SidebarProvider(
|
||||
context.extensionUri,
|
||||
lspClient,
|
||||
optimizationService,
|
||||
analysisService,
|
||||
navigationService,
|
||||
optimizationEventEmitter,
|
||||
globalState,
|
||||
() => codeLensProvider.refresh(),
|
||||
);
|
||||
const codeLensProvider = new CodeflashCodeLensProvider(
|
||||
lspClient,
|
||||
analysisService,
|
||||
navigationService,
|
||||
globalState,
|
||||
);
|
||||
|
||||
// Register CodeLens provider for Python files
|
||||
const codeLensDisposable = vscode.languages.registerCodeLensProvider(
|
||||
{ language: "python", scheme: "file" },
|
||||
codeLensProvider,
|
||||
);
|
||||
|
||||
this.provideCommands(sidebarProvider);
|
||||
|
||||
this._disposables.push(
|
||||
codeLensDisposable,
|
||||
analysisService,
|
||||
optimizationService,
|
||||
navigationService,
|
||||
sidebarProvider,
|
||||
codeLensProvider,
|
||||
);
|
||||
return {
|
||||
analysisService,
|
||||
optimizationService,
|
||||
navigationService,
|
||||
sidebarProvider,
|
||||
codeLensProvider,
|
||||
};
|
||||
};
|
||||
|
||||
private provideCommands = (sidebarProvider: SidebarProvider) => {
|
||||
const optimizeFunctionCommand = vscode.commands.registerCommand(
|
||||
"codeflash.optimizeFunction",
|
||||
(uri: vscode.Uri, functionName: string) => {
|
||||
sidebarProvider.addFunctionToQueue(functionName, uri, true);
|
||||
},
|
||||
);
|
||||
|
||||
const optimizeAllCommand = vscode.commands.registerCommand(
|
||||
"codeflash.optimizeAll",
|
||||
async () => {
|
||||
this.logger.info("Optimizing all functions from command");
|
||||
await sidebarProvider.handleOptimizeAllInCurrentFile();
|
||||
},
|
||||
);
|
||||
|
||||
const viewOptimization = vscode.commands.registerCommand(
|
||||
"codeflash.viewPatch",
|
||||
async (payload: ViewPatchMessage["payload"]) => {
|
||||
await sidebarProvider.handleViewPatch(payload);
|
||||
},
|
||||
);
|
||||
const sendMessage = vscode.commands.registerCommand(
|
||||
"codeflash.sendMessage",
|
||||
(message: OutgoingWebviewMessage) => {
|
||||
sidebarProvider.sendMessage(message);
|
||||
},
|
||||
);
|
||||
|
||||
this._disposables.push(
|
||||
optimizeFunctionCommand,
|
||||
optimizeAllCommand,
|
||||
viewOptimization,
|
||||
sendMessage,
|
||||
);
|
||||
};
|
||||
private registerGlobalLogsListener = () => {
|
||||
const optimizationEventListener = this.optimizationEventEmitter.event(
|
||||
(message) => {
|
||||
const currentRunningTask = this.globalState
|
||||
.get(GlobalStateKey.QueueTasks)
|
||||
?.find(isTaskRunning);
|
||||
if (!currentRunningTask) {
|
||||
return;
|
||||
}
|
||||
if (!isTaskRunning(currentRunningTask)) {
|
||||
this.logger.debug(
|
||||
`Task ${currentRunningTask.id} is not running, skipping log entry with message: ${message}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
currentRunningTask.logs.push(message);
|
||||
},
|
||||
);
|
||||
this._disposables.push(optimizationEventListener);
|
||||
};
|
||||
}
|
||||
130
js/VSC-Extension/src/boot/checkEnvironmentStep.ts
Normal file
130
js/VSC-Extension/src/boot/checkEnvironmentStep.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import type { StepError } from "./baseStep";
|
||||
import BaseStep from "./baseStep";
|
||||
import * as vscode from "vscode";
|
||||
import { type GitExtension } from "../types/git";
|
||||
import { getRootWorkspaceFolder } from "../utils/rootWorkspace";
|
||||
import { PythonService } from "../services";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { Logger } from "../utils";
|
||||
import {
|
||||
getCodeflashNotInstalledError,
|
||||
getNoGitRepoError,
|
||||
getPythonNotSelectedError,
|
||||
} from "./stepErrors";
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
export type CheckEnvironmentStepResult = {
|
||||
pythonPath: string;
|
||||
};
|
||||
|
||||
export class CheckEnvironmentStep extends BaseStep<CheckEnvironmentStepResult> {
|
||||
gitExtension =
|
||||
vscode.extensions.getExtension<GitExtension>("vscode.git")?.exports!;
|
||||
logger = new Logger("CheckEnvironmentStep");
|
||||
pythonService = new PythonService();
|
||||
|
||||
private pythonPath?: string;
|
||||
|
||||
get pythonInterpreter(): string | undefined {
|
||||
return this.pythonPath;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super("Checking your environment");
|
||||
}
|
||||
|
||||
public override async run(
|
||||
...args: unknown[]
|
||||
): Promise<StepError | CheckEnvironmentStepResult> {
|
||||
const [] = args;
|
||||
const result: CheckEnvironmentStepResult = {
|
||||
pythonPath: "",
|
||||
};
|
||||
|
||||
const pythonVenv = await this.validatePythonEnvironment();
|
||||
if (pythonVenv.pythonPath === "") {
|
||||
return getPythonNotSelectedError();
|
||||
}
|
||||
|
||||
result.pythonPath = pythonVenv.pythonPath;
|
||||
|
||||
const [gitOk, codeflashInstalled] = await Promise.all([
|
||||
this.isGitUsedInRepo(),
|
||||
this.isCodeflashInstalled(pythonVenv.pythonPath),
|
||||
]);
|
||||
|
||||
if (!gitOk) {
|
||||
return getNoGitRepoError();
|
||||
}
|
||||
if (!codeflashInstalled) {
|
||||
return getCodeflashNotInstalledError(pythonVenv.pythonPath);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private isGitUsedInRepo = async (): Promise<boolean> => {
|
||||
const workspaceFolder = getRootWorkspaceFolder();
|
||||
if (!workspaceFolder) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await execPromise("git rev-parse --is-inside-work-tree", {
|
||||
cwd: workspaceFolder.uri.fsPath,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
"Error checking for git repository",
|
||||
error instanceof Error ? error : undefined,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private validatePythonEnvironment = async (): Promise<{
|
||||
pythonPath: string;
|
||||
}> => {
|
||||
const workspaceFolder = getRootWorkspaceFolder();
|
||||
|
||||
if (!workspaceFolder) {
|
||||
return {
|
||||
pythonPath: "",
|
||||
};
|
||||
}
|
||||
|
||||
const { errorConfig, pythonPath } =
|
||||
await this.pythonService.validateDependencies();
|
||||
|
||||
if (errorConfig) {
|
||||
return {
|
||||
pythonPath: "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
pythonPath: pythonPath!,
|
||||
};
|
||||
};
|
||||
|
||||
private isCodeflashInstalled = async (
|
||||
pythonPath: string,
|
||||
): Promise<boolean> => {
|
||||
const cmd = `${pythonPath} -c "import importlib.metadata as m; print(m.version('codeflash'))"`;
|
||||
try {
|
||||
// TODO: from the stdout get the codeflash version and check if it's compatible
|
||||
await execPromise(cmd);
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
this.logger.debug("Error checking for codeflash installation " + message);
|
||||
if (message.includes("PackageNotFoundError")) {
|
||||
return false;
|
||||
}
|
||||
// for other unexpected errors, we don't block the user
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
80
js/VSC-Extension/src/boot/codeflashInitStep.ts
Normal file
80
js/VSC-Extension/src/boot/codeflashInitStep.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import type { LanguageClient } from "vscode-languageclient/node";
|
||||
import { LSP_COMMANDS } from "../constants";
|
||||
import type { GlobalState } from "../globalState";
|
||||
import { GlobalStateKey } from "../globalState";
|
||||
import type { OptimizationResponse } from "../types";
|
||||
import { Logger } from "../utils";
|
||||
import { getRootWorkspaceFolder } from "../utils/rootWorkspace";
|
||||
import BaseStep, { StepError } from "./baseStep";
|
||||
|
||||
export interface CodeflashInitResult {}
|
||||
|
||||
export class CodeflashInitStep extends BaseStep<CodeflashInitResult> {
|
||||
logger = new Logger("CodeflashInitStep");
|
||||
constructor(
|
||||
private readonly lspClient: LanguageClient,
|
||||
private readonly optimizationClient: LanguageClient,
|
||||
private readonly globalState: GlobalState,
|
||||
) {
|
||||
super("Almost there!");
|
||||
}
|
||||
|
||||
public async run(): Promise<CodeflashInitResult | StepError> {
|
||||
const workspaceFolder = getRootWorkspaceFolder();
|
||||
const { status, message, moduleRoot, pyprojectPath, root } =
|
||||
await this.lspClient.sendRequest<{
|
||||
status: string;
|
||||
message?: string;
|
||||
moduleRoot?: string;
|
||||
root?: string;
|
||||
pyprojectPath: string;
|
||||
}>(LSP_COMMANDS.INIT_PROJECT, {
|
||||
root_path_abs: workspaceFolder?.uri?.fsPath,
|
||||
});
|
||||
|
||||
this.globalState.set(GlobalStateKey.RootDir, root);
|
||||
this.globalState.set(GlobalStateKey.ModuleRoot, moduleRoot);
|
||||
|
||||
if (status !== "success") {
|
||||
this.lspClient.stop();
|
||||
this.optimizationClient.stop();
|
||||
|
||||
return new StepError(
|
||||
`Failed to initialize Codeflash: ${message || "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this.checkApiKey(),
|
||||
this.optimizationClient.sendRequest(LSP_COMMANDS.INIT_PROJECT, {
|
||||
config_file: pyprojectPath,
|
||||
root_path_abs: workspaceFolder,
|
||||
skip_validation: true, // was already validated by the main client
|
||||
}),
|
||||
]);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private checkApiKey = async (): Promise<boolean> => {
|
||||
if (!this.lspClient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await this.lspClient.sendRequest<OptimizationResponse>(
|
||||
LSP_COMMANDS.CHECK_API_KEY,
|
||||
{},
|
||||
);
|
||||
|
||||
if (result.status === "error") {
|
||||
this.logger.error(`Invalid API key, message: ${result.message}`);
|
||||
return false;
|
||||
} else if (result.status === "success") {
|
||||
this.globalState.set(GlobalStateKey.UserID, result.user_id as string);
|
||||
this.logger.info(`user id: ${result.user_id}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
35
js/VSC-Extension/src/boot/stepErrors.ts
Normal file
35
js/VSC-Extension/src/boot/stepErrors.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { StepError } from "./baseStep";
|
||||
|
||||
export const getPythonNotSelectedError = (): StepError => {
|
||||
const err = new StepError("No Python interpreter selected");
|
||||
err.details =
|
||||
"No Python interpreter selected in the current workspace, are you sure you have python installed?";
|
||||
err.vscodeActionCommand = {
|
||||
title: "",
|
||||
btnText: "Select interpreter",
|
||||
command: "python.setInterpreter",
|
||||
};
|
||||
return err;
|
||||
};
|
||||
|
||||
export const getCodeflashNotInstalledError = (
|
||||
interpreterPath: string,
|
||||
): StepError => {
|
||||
const err = new StepError("Codeflash is not installed");
|
||||
err.details = `Codeflash is not installed in the current active Python environment (<code>${interpreterPath}</code>).`;
|
||||
err.vscodeActionCommand = {
|
||||
title: "Or you can select another interpreter:",
|
||||
btnText: "Select interpreter",
|
||||
command: "python.setInterpreter",
|
||||
};
|
||||
err.helperCmdText = "You can install it by using this command:";
|
||||
err.helperCmd = `${interpreterPath} -m pip install codeflash`;
|
||||
return err;
|
||||
};
|
||||
|
||||
export const getNoGitRepoError = (): StepError => {
|
||||
const err = new StepError("No git repository found");
|
||||
err.details = "No git repository found in the current workspace.";
|
||||
// TODO: create a command to initialize and create initial commit in the current workspace
|
||||
return err;
|
||||
};
|
||||
|
|
@ -21,7 +21,6 @@ export const LSP_COMMANDS = {
|
|||
PERFORM_FUNCTION_OPTIMIZATION: "performFunctionOptimization",
|
||||
INIT_PROJECT: "initProject",
|
||||
ON_PATCH_APPLIED: "onPatchApplied",
|
||||
RETRIEVE_SUCCESSFUL_OPTS: "retrieveSuccessfulOptimizations",
|
||||
} as const;
|
||||
|
||||
export const PYTHON_EXTENSION_ID = "ms-python.python";
|
||||
|
|
|
|||
|
|
@ -1,42 +1,16 @@
|
|||
import * as vscode from "vscode";
|
||||
import { SidebarProvider } from "./providers/SidebarProvider";
|
||||
import { CodeflashCodeLensProvider } from "./providers/CodeLensProvider";
|
||||
import {
|
||||
PythonService,
|
||||
LspService,
|
||||
AnalysisService,
|
||||
OptimizationService,
|
||||
NavigationService,
|
||||
codeflashInitErrorConfig,
|
||||
LSP_FAILED_MSG_TITLE,
|
||||
} from "./services";
|
||||
import {
|
||||
LSP_COMMANDS,
|
||||
PYTHON_EXTENSION_ID,
|
||||
SIDEBAR_VIEW_ID,
|
||||
} from "./constants";
|
||||
import { PYTHON_EXTENSION_ID, SIDEBAR_VIEW_ID } from "./constants";
|
||||
import { Logger } from "./utils";
|
||||
import { LoadingWebview } from "./utils/LoadingWebview";
|
||||
import type { LanguageClient } from "vscode-languageclient/node";
|
||||
import type { ErrorWebviewConfig } from "./types";
|
||||
import { ErrorWebview } from "./utils/ErrorWebview";
|
||||
import { isTaskRunning } from "@codeflash/shared";
|
||||
|
||||
import { exec } from "child_process";
|
||||
import * as util from "util";
|
||||
|
||||
const execPromise = util.promisify(exec);
|
||||
|
||||
import type { PythonExtension } from "@vscode/python-extension";
|
||||
import { WebviewSwitcherProvider } from "./providers/webviewSwitcherProvider";
|
||||
import type { LogEntry } from "@codeflash/types";
|
||||
import { GlobalState, GlobalStateKey } from "./globalState";
|
||||
|
||||
import { GlobalState } from "./globalState";
|
||||
import { InitWebviewProvider } from "./providers/InitWebviewProvider";
|
||||
import { InitService } from "./services/initService";
|
||||
|
||||
let logger: Logger = new Logger("Codeflash Extension");
|
||||
const webviewSwitcherProvider = new WebviewSwitcherProvider(
|
||||
new LoadingWebview(),
|
||||
);
|
||||
|
||||
const sidebarOptions = { webviewOptions: { retainContextWhenHidden: true } };
|
||||
|
||||
const addDisposable = (
|
||||
|
|
@ -46,99 +20,6 @@ const addDisposable = (
|
|||
ctx.subscriptions.push(...inputDisposables);
|
||||
};
|
||||
|
||||
// should be called only once
|
||||
const registerWebviewProvider = (context: vscode.ExtensionContext) => {
|
||||
addDisposable(
|
||||
context,
|
||||
vscode.window.registerWebviewViewProvider(
|
||||
SIDEBAR_VIEW_ID,
|
||||
webviewSwitcherProvider,
|
||||
sidebarOptions,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const provideServices = (
|
||||
context: vscode.ExtensionContext,
|
||||
globalState: GlobalState,
|
||||
lspClient: LanguageClient,
|
||||
optimizationClient: LanguageClient,
|
||||
optimizationEventEmitter: vscode.EventEmitter<LogEntry>,
|
||||
) => {
|
||||
const analysisService = new AnalysisService(lspClient);
|
||||
const optimizationService = new OptimizationService(optimizationClient);
|
||||
const navigationService = new NavigationService();
|
||||
|
||||
const sidebarProvider = new SidebarProvider(
|
||||
context.extensionUri,
|
||||
lspClient,
|
||||
optimizationService,
|
||||
analysisService,
|
||||
navigationService,
|
||||
optimizationEventEmitter,
|
||||
globalState,
|
||||
);
|
||||
const codeLensProvider = new CodeflashCodeLensProvider(
|
||||
lspClient,
|
||||
analysisService,
|
||||
navigationService,
|
||||
);
|
||||
|
||||
codeLensProvider.refresh(500); // refresh to get the code lenses of the current file
|
||||
|
||||
// Register CodeLens provider for Python files
|
||||
const codeLensDisposable = vscode.languages.registerCodeLensProvider(
|
||||
{ language: "python", scheme: "file" },
|
||||
codeLensProvider,
|
||||
);
|
||||
|
||||
provideCommands(context, sidebarProvider);
|
||||
webviewSwitcherProvider.switchTo(sidebarProvider);
|
||||
addDisposable(
|
||||
context,
|
||||
codeLensDisposable,
|
||||
optimizationService,
|
||||
analysisService,
|
||||
optimizationService,
|
||||
navigationService,
|
||||
sidebarProvider,
|
||||
codeLensProvider,
|
||||
);
|
||||
};
|
||||
|
||||
const provideLspServices = async (
|
||||
context: vscode.ExtensionContext,
|
||||
pythonPath: string,
|
||||
optimizationEventEmitter: vscode.EventEmitter<LogEntry>,
|
||||
): Promise<{
|
||||
lspService: LspService;
|
||||
optimizationLspService: LspService;
|
||||
}> => {
|
||||
var startTime = performance.now();
|
||||
const lspService = new LspService("Main");
|
||||
|
||||
// create another client for optimization as we are using a single pipe per client, so using a single client would be
|
||||
// mean waiting for the optimization to be finished before calling any other lsp features
|
||||
const optimizationLspService = new LspService(
|
||||
"Optimization",
|
||||
optimizationEventEmitter,
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
lspService.start(pythonPath, false),
|
||||
optimizationLspService.start(pythonPath, true),
|
||||
]);
|
||||
|
||||
logger.debug("LSP client is ready and running.");
|
||||
|
||||
var endTime = performance.now();
|
||||
logger.info(`Step 2: Starting LSP client took ${endTime - startTime} ms`);
|
||||
|
||||
addDisposable(context, lspService, optimizationLspService);
|
||||
|
||||
return { lspService, optimizationLspService };
|
||||
};
|
||||
|
||||
const provideReloadExtensionCommand = (
|
||||
context: vscode.ExtensionContext,
|
||||
): vscode.Disposable => {
|
||||
|
|
@ -149,7 +30,11 @@ const provideReloadExtensionCommand = (
|
|||
while (context.subscriptions.length) {
|
||||
const disposable = context.subscriptions.pop();
|
||||
if (disposable) {
|
||||
await disposable.dispose();
|
||||
try {
|
||||
await disposable.dispose();
|
||||
} catch (err) {
|
||||
logger.error("failed to dispose", err as Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// wait for the activation. Mainly to avoid the registeration of the reload command while the existing one is still being disposed which will cause multiple reload command handlers being invoked.
|
||||
|
|
@ -162,55 +47,6 @@ const provideReloadExtensionCommand = (
|
|||
return reloadExtensionCommand;
|
||||
};
|
||||
|
||||
const provideCommands = (
|
||||
context: vscode.ExtensionContext,
|
||||
sidebarProvider: SidebarProvider,
|
||||
) => {
|
||||
const optimizeFunctionCommand = vscode.commands.registerCommand(
|
||||
"codeflash.optimizeFunction",
|
||||
(uri: vscode.Uri, functionName: string) => {
|
||||
sidebarProvider.addFunctionToQueue(functionName, uri, true);
|
||||
},
|
||||
);
|
||||
|
||||
const optimizeAllCommand = vscode.commands.registerCommand(
|
||||
"codeflash.optimizeAll",
|
||||
async () => {
|
||||
logger?.info("Optimizing all functions from command");
|
||||
await sidebarProvider.handleOptimizeAllInCurrentFile();
|
||||
},
|
||||
);
|
||||
|
||||
addDisposable(context, optimizeFunctionCommand, optimizeAllCommand);
|
||||
};
|
||||
|
||||
const getRootWorkspaceFolder = (): vscode.WorkspaceFolder | null => {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (!workspaceFolder) {
|
||||
return null;
|
||||
}
|
||||
return workspaceFolder;
|
||||
};
|
||||
|
||||
const isGitUsedInRepo = async (): Promise<boolean> => {
|
||||
const workspaceFolder = getRootWorkspaceFolder();
|
||||
if (!workspaceFolder) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await execPromise("git rev-parse --is-inside-work-tree", {
|
||||
cwd: workspaceFolder.uri.fsPath,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"Error checking for git repository",
|
||||
error instanceof Error ? error : undefined,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function setupInterpreterWatcher(): vscode.Disposable {
|
||||
const pythonExtension =
|
||||
vscode.extensions.getExtension<PythonExtension>(PYTHON_EXTENSION_ID)!;
|
||||
|
|
@ -226,214 +62,56 @@ function setupInterpreterWatcher(): vscode.Disposable {
|
|||
return interpreterWatcher;
|
||||
}
|
||||
|
||||
const validatePythonEnvironment = async (): Promise<{
|
||||
pythonPath: string;
|
||||
errorConfig?: ErrorWebviewConfig;
|
||||
}> => {
|
||||
var startTime = performance.now();
|
||||
const workspaceFolder = getRootWorkspaceFolder();
|
||||
|
||||
if (!workspaceFolder) {
|
||||
return {
|
||||
pythonPath: "",
|
||||
errorConfig: {
|
||||
icon: "warning",
|
||||
title: "No Workspace Folder Detected",
|
||||
details: "Please open a folder or workspace to use Codeflash.",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const pythonService = new PythonService();
|
||||
const { errorConfig, pythonPath } =
|
||||
await pythonService.validateDependencies();
|
||||
|
||||
if (errorConfig) {
|
||||
vscode.window.showErrorMessage(errorConfig.title);
|
||||
return {
|
||||
pythonPath: "",
|
||||
errorConfig,
|
||||
};
|
||||
}
|
||||
|
||||
var endTime = performance.now();
|
||||
logger.info(
|
||||
`Step 1: Validating Python environment took ${endTime - startTime} ms`,
|
||||
);
|
||||
return {
|
||||
pythonPath: pythonPath!,
|
||||
};
|
||||
};
|
||||
const webviewSwitcherProvider = new WebviewSwitcherProvider({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
resolveWebviewView(_webviewView, _context, _token) {},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export async function activate(
|
||||
context: vscode.ExtensionContext,
|
||||
): Promise<void> {
|
||||
logger.info("Activating Codeflash extension...");
|
||||
addDisposable(context, provideReloadExtensionCommand(context));
|
||||
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "Initializing Codeflash extension...",
|
||||
cancellable: false,
|
||||
},
|
||||
const optimizationEventEmitter = new vscode.EventEmitter<LogEntry>(); // for emitting events from the lsp server logs to the sidebar, for tracking the running optimization progress
|
||||
const globalState = new GlobalState(context);
|
||||
const showGlobalStateCommand = vscode.commands.registerCommand(
|
||||
"codeflash.dev.showGlobalState",
|
||||
async () => {
|
||||
await doActivate(context);
|
||||
await globalState.showGlobalStateValue();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function doActivate(
|
||||
context: vscode.ExtensionContext,
|
||||
): Promise<void> {
|
||||
const totalStartTime = performance.now();
|
||||
logger.info("Activating Codeflash extension...");
|
||||
try {
|
||||
const optimizationEventEmitter = new vscode.EventEmitter<LogEntry>(); // for emitting events from the lsp server logs to the sidebar, for tracking the running optimization progress
|
||||
const globalState = new GlobalState(context);
|
||||
|
||||
const showGlobalStateCommand = vscode.commands.registerCommand(
|
||||
"codeflash.dev.showGlobalState",
|
||||
async () => {
|
||||
await globalState.showGlobalStateValue();
|
||||
},
|
||||
);
|
||||
|
||||
addDisposable(context, setupInterpreterWatcher(), showGlobalStateCommand);
|
||||
registerWebviewProvider(context);
|
||||
webviewSwitcherProvider.switchTo(new LoadingWebview());
|
||||
|
||||
const isGitUsed = await isGitUsedInRepo();
|
||||
if (!isGitUsed) {
|
||||
vscode.window.showErrorMessage(
|
||||
"Codeflash requires git to be used in the repository, initialize a new git repository and restart the editor",
|
||||
);
|
||||
webviewSwitcherProvider.switchTo(
|
||||
new ErrorWebview(
|
||||
{
|
||||
icon: "warning",
|
||||
title: "No Git Repository Found",
|
||||
details:
|
||||
'You must initialize a new git repository to use <span class="highlight">codeflash</span>.',
|
||||
terminalCommand: "git init",
|
||||
},
|
||||
context.extensionUri,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { pythonPath, errorConfig } = await validatePythonEnvironment();
|
||||
if (errorConfig) {
|
||||
webviewSwitcherProvider.switchTo(
|
||||
new ErrorWebview(errorConfig, context.extensionUri),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { lspService, optimizationLspService } = await provideLspServices(
|
||||
context,
|
||||
pythonPath,
|
||||
optimizationEventEmitter,
|
||||
);
|
||||
const lspClient = lspService.getClient();
|
||||
const optimizationClient = optimizationLspService.getClient();
|
||||
|
||||
if (!lspClient || !optimizationClient) {
|
||||
throw new Error(LSP_FAILED_MSG_TITLE);
|
||||
}
|
||||
|
||||
var startTime = performance.now();
|
||||
const workspaceFolder = getRootWorkspaceFolder();
|
||||
const { status, message, moduleRoot, pyprojectPath } =
|
||||
await lspClient.sendRequest<{
|
||||
status: string;
|
||||
message?: string;
|
||||
moduleRoot?: string;
|
||||
pyprojectPath: string;
|
||||
}>(LSP_COMMANDS.INIT_PROJECT, {
|
||||
root_path_abs: workspaceFolder?.uri?.fsPath,
|
||||
});
|
||||
|
||||
globalState.set(GlobalStateKey.ModuleRoot, moduleRoot);
|
||||
|
||||
if (status !== "success") {
|
||||
lspClient.stop();
|
||||
optimizationClient.stop();
|
||||
webviewSwitcherProvider.switchTo(
|
||||
new ErrorWebview(
|
||||
message?.includes("pyproject")
|
||||
? codeflashInitErrorConfig
|
||||
: {
|
||||
icon: "warning",
|
||||
title: "Project Validation Failed",
|
||||
details: message || "",
|
||||
},
|
||||
context.extensionUri,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
(await optimizationClient.sendRequest(LSP_COMMANDS.INIT_PROJECT, {
|
||||
config_file: pyprojectPath,
|
||||
root_path_abs: workspaceFolder,
|
||||
skip_validation: true, // was already validated by the main client
|
||||
}),
|
||||
provideServices(
|
||||
context,
|
||||
globalState,
|
||||
lspClient,
|
||||
optimizationClient,
|
||||
optimizationEventEmitter,
|
||||
));
|
||||
|
||||
var endTime = performance.now();
|
||||
logger.info(
|
||||
`Step 4: Project initialization and validation took ${endTime - startTime} ms`,
|
||||
);
|
||||
const totalEndTime = performance.now();
|
||||
logger.info(
|
||||
`✅ Codeflash extension activation complete, took ${totalEndTime - totalStartTime} ms.`,
|
||||
);
|
||||
const optimizationEventListener = optimizationEventEmitter.event(
|
||||
(message) => {
|
||||
const currentRunningTask = globalState
|
||||
.get(GlobalStateKey.QueueTasks)
|
||||
?.find(isTaskRunning);
|
||||
if (!currentRunningTask) {
|
||||
return;
|
||||
}
|
||||
if (!isTaskRunning(currentRunningTask)) {
|
||||
logger.debug(
|
||||
`Task ${currentRunningTask.id} is not running, skipping log entry with message: ${message}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
currentRunningTask.logs.push(message);
|
||||
},
|
||||
);
|
||||
addDisposable(context, optimizationEventListener);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const showIntepreterSelectorBtn =
|
||||
errorMessage.includes(LSP_FAILED_MSG_TITLE);
|
||||
|
||||
webviewSwitcherProvider.switchTo(
|
||||
new ErrorWebview(
|
||||
{
|
||||
icon: "error",
|
||||
title: "Codeflash Activation Failed",
|
||||
details: errorMessage,
|
||||
actionButton: showIntepreterSelectorBtn
|
||||
? {
|
||||
text: "Select Python Interpreter",
|
||||
onClick: () => {
|
||||
vscode.commands.executeCommand("python.setInterpreter");
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
context.extensionUri,
|
||||
),
|
||||
);
|
||||
}
|
||||
addDisposable(context, setupInterpreterWatcher(), showGlobalStateCommand);
|
||||
|
||||
const initService = new InitService(
|
||||
context,
|
||||
globalState,
|
||||
optimizationEventEmitter,
|
||||
);
|
||||
|
||||
const initWebviewProvider = new InitWebviewProvider(
|
||||
context.extensionUri,
|
||||
initService,
|
||||
);
|
||||
|
||||
addDisposable(
|
||||
context,
|
||||
initService,
|
||||
vscode.window.registerWebviewViewProvider(
|
||||
SIDEBAR_VIEW_ID,
|
||||
webviewSwitcherProvider,
|
||||
sidebarOptions,
|
||||
),
|
||||
initWebviewProvider,
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
webviewSwitcherProvider.switchTo(initWebviewProvider);
|
||||
const result = await initService.initWithLoading();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
logger.info("Init completed");
|
||||
webviewSwitcherProvider.switchTo(result.bootResult.sidebarProvider);
|
||||
}, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,69 @@
|
|||
import type { QueueTaskItem } from "@codeflash/types";
|
||||
import type { ExtensionContext} from "vscode";
|
||||
import type { ExtensionContext } from "vscode";
|
||||
import { window, workspace } from "vscode";
|
||||
|
||||
export const enum GlobalStateKey {
|
||||
Running = "running",
|
||||
UserID = "user_id",
|
||||
ExistingOptimizationFetched = "existingOptimizationFetched",
|
||||
QueueTasks = "queueTasks",
|
||||
FocusedTaskId = "focusedTaskId",
|
||||
RootDir = "rootDir", // absolute path to the root of the workspace (e.g. where .git is located)
|
||||
ModuleRoot = "moduleRoot",
|
||||
ContextFiles = "contextFiles",
|
||||
}
|
||||
|
||||
export type ContextFiles = Record<string, string>;
|
||||
|
||||
interface GlobalStateKeyMapping {
|
||||
[GlobalStateKey.Running]: boolean;
|
||||
[GlobalStateKey.UserID]: string | undefined;
|
||||
[GlobalStateKey.ExistingOptimizationFetched]: boolean;
|
||||
[GlobalStateKey.QueueTasks]: QueueTaskItem[];
|
||||
[GlobalStateKey.FocusedTaskId]: string | undefined;
|
||||
[GlobalStateKey.RootDir]: string | undefined;
|
||||
[GlobalStateKey.ModuleRoot]: string | undefined;
|
||||
[GlobalStateKey.ContextFiles]: Record<string, ContextFiles> | undefined;
|
||||
}
|
||||
|
||||
const workspaceSpecificKeys = [
|
||||
GlobalStateKey.RootDir,
|
||||
GlobalStateKey.ModuleRoot,
|
||||
GlobalStateKey.ContextFiles,
|
||||
GlobalStateKey.QueueTasks,
|
||||
];
|
||||
|
||||
export class GlobalState {
|
||||
constructor(private context: ExtensionContext) {
|
||||
const successfulTasks = (
|
||||
this.get(GlobalStateKey.QueueTasks, []) ?? []
|
||||
).filter((task) => task.status === "completed");
|
||||
// remove context files of non-successful tasks
|
||||
const cleanContextFiles = this.get(GlobalStateKey.ContextFiles, {})!;
|
||||
for (const taskId of Object.keys(cleanContextFiles)) {
|
||||
if (!successfulTasks.find((task) => task.id === taskId)) {
|
||||
delete cleanContextFiles[taskId];
|
||||
}
|
||||
}
|
||||
this.set(GlobalStateKey.ContextFiles, cleanContextFiles);
|
||||
this.set(GlobalStateKey.QueueTasks, successfulTasks);
|
||||
|
||||
// reset state values
|
||||
this.set(GlobalStateKey.Running, false);
|
||||
this.set(GlobalStateKey.UserID, undefined); // TODO: should we preserve user id?
|
||||
this.set(GlobalStateKey.ExistingOptimizationFetched, false);
|
||||
this.set(GlobalStateKey.QueueTasks, []); // TODO (ali): preserve the completed ones only, and remove the persistence logic from the lsp server, same goes for patch files, preserve the raw patch also
|
||||
this.set(GlobalStateKey.FocusedTaskId, undefined);
|
||||
this.set(GlobalStateKey.ModuleRoot, "");
|
||||
this.set(GlobalStateKey.RootDir, "");
|
||||
}
|
||||
|
||||
get<T extends GlobalStateKeyMapping, E extends keyof T>(
|
||||
stateKey: E,
|
||||
defaultValue?: T[E],
|
||||
): T[E] | undefined {
|
||||
if (workspaceSpecificKeys.includes(stateKey as GlobalStateKey)) {
|
||||
return (
|
||||
this.context.workspaceState.get(stateKey as string) ?? defaultValue
|
||||
);
|
||||
}
|
||||
|
||||
return this.context.globalState.get(stateKey as string) ?? defaultValue;
|
||||
}
|
||||
|
||||
|
|
@ -42,17 +71,38 @@ export class GlobalState {
|
|||
stateKey: E,
|
||||
newValue: T[E],
|
||||
): void {
|
||||
if (workspaceSpecificKeys.includes(stateKey as GlobalStateKey)) {
|
||||
this.context.workspaceState.update(stateKey as string, newValue);
|
||||
return;
|
||||
}
|
||||
this.context.globalState.update(stateKey as string, newValue);
|
||||
}
|
||||
|
||||
getStateForWebview(): Partial<{
|
||||
[key in keyof GlobalStateKeyMapping]: GlobalStateKeyMapping[key];
|
||||
}> {
|
||||
return {
|
||||
running: this.get(GlobalStateKey.Running, false)!,
|
||||
focusedTaskId: this.get(GlobalStateKey.FocusedTaskId)!,
|
||||
queueTasks: this.get(GlobalStateKey.QueueTasks, [])!,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Run while developing to see the entire global storage contents.
|
||||
*/
|
||||
async showGlobalStateValue() {
|
||||
const all: Record<string, unknown> = {};
|
||||
for (const key of this.context.globalState.keys()) {
|
||||
all[key] = this.get(key as GlobalStateKey);
|
||||
}
|
||||
for (const key of this.context.workspaceState.keys()) {
|
||||
all[key] = this.get(key as GlobalStateKey);
|
||||
}
|
||||
|
||||
const document = await workspace.openTextDocument({
|
||||
language: "jsonc",
|
||||
// @ts-ignore
|
||||
content: JSON.stringify(this.context.globalState._value, null, " "),
|
||||
content: JSON.stringify(all, null, " "),
|
||||
});
|
||||
window.showTextDocument(document);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import { State as LanguageClientState } from "vscode-languageclient/node";
|
|||
import { Logger } from "../utils";
|
||||
import debounce from "debounce";
|
||||
import type { AnalysisService, NavigationService } from "../services";
|
||||
import { Disposable } from "../utils/dispose";
|
||||
import { GlobalStateKey, type GlobalState } from "../globalState";
|
||||
import type { QueueTaskItem, ViewPatchMessage } from "@codeflash/types";
|
||||
import type { OutgoingWebviewMessage } from "../types";
|
||||
|
||||
interface OptimizationSuggestion {
|
||||
functionName: string;
|
||||
|
|
@ -14,11 +18,11 @@ interface OptimizationSuggestion {
|
|||
severity: "info" | "warning" | "suggestion";
|
||||
}
|
||||
|
||||
export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
||||
private readonly _logger: Logger;
|
||||
private readonly _disposables: vscode.Disposable[] = [];
|
||||
private _analysisService: AnalysisService;
|
||||
private _navigationService: NavigationService;
|
||||
export class CodeflashCodeLensProvider
|
||||
extends Disposable
|
||||
implements vscode.CodeLensProvider
|
||||
{
|
||||
private readonly logger = new Logger("Codeflash CodeLens");
|
||||
private _onDidChangeCodeLenses = new vscode.EventEmitter<void>();
|
||||
private _cachedSuggestions = new Map<string, OptimizationSuggestion[]>();
|
||||
private _debouncedRefresh: () => void;
|
||||
|
|
@ -27,17 +31,16 @@ export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
|||
this._onDidChangeCodeLenses.event;
|
||||
|
||||
constructor(
|
||||
private readonly _lspClient: LanguageClient,
|
||||
analysisService: AnalysisService,
|
||||
navigationService: NavigationService,
|
||||
private readonly lspClient: LanguageClient,
|
||||
private readonly analysisService: AnalysisService,
|
||||
private readonly navigationService: NavigationService,
|
||||
private readonly globalState: GlobalState,
|
||||
) {
|
||||
this._logger = new Logger("Codeflash CodeLens");
|
||||
this._analysisService = analysisService;
|
||||
this._navigationService = navigationService;
|
||||
super();
|
||||
|
||||
// Refresh CodeLenses when LSP state changes
|
||||
this._disposables.push(
|
||||
this._lspClient.onDidChangeState((e) => {
|
||||
this.lspClient.onDidChangeState((e) => {
|
||||
if (e.newState === LanguageClientState.Running) {
|
||||
this._onDidChangeCodeLenses.fire();
|
||||
}
|
||||
|
|
@ -62,14 +65,18 @@ export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
|||
if (document.languageId !== "python") {
|
||||
return [];
|
||||
}
|
||||
// codelens can't be generated until the lsp server optimizer is initialized, which is after the lsp validate the user api key.
|
||||
if (!this.globalState.get(GlobalStateKey.UserID)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this._lspClient.state !== LanguageClientState.Running) {
|
||||
this._logger.warn(
|
||||
`CodeLens requested but LSP client not running. State: ${this._lspClient.state}`,
|
||||
if (this.lspClient.state !== LanguageClientState.Running) {
|
||||
this.logger.warn(
|
||||
`CodeLens requested but LSP client not running. State: ${this.lspClient.state}`,
|
||||
);
|
||||
|
||||
// Show user-friendly message for LSP issues
|
||||
if (this._lspClient.state === LanguageClientState.Stopped) {
|
||||
if (this.lspClient.state === LanguageClientState.Stopped) {
|
||||
vscode.window
|
||||
.showWarningMessage(
|
||||
"Codeflash Language Server is not running. CodeLens suggestions unavailable.",
|
||||
|
|
@ -87,7 +94,7 @@ export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
|||
|
||||
try {
|
||||
// Get optimizable functions for this document
|
||||
const result = await this._analysisService.getOptimizableFunctions(
|
||||
const result = await this.analysisService.getOptimizableFunctions(
|
||||
document.uri,
|
||||
);
|
||||
|
||||
|
|
@ -96,7 +103,7 @@ export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
|||
!result.functions ||
|
||||
result.functions.length === 0
|
||||
) {
|
||||
this._logger.warn(
|
||||
this.logger.warn(
|
||||
`No optimizable functions found for ${document.uri.fsPath} : ${result.message}`,
|
||||
);
|
||||
return [];
|
||||
|
|
@ -107,7 +114,7 @@ export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
|||
// Create CodeLenses for optimizable functions
|
||||
for (const functionName of result.functions) {
|
||||
// Find function position in document
|
||||
const pos = await this._navigationService.manuallyFindTheFunctionInFile(
|
||||
const pos = await this.navigationService.manuallyFindTheFunctionInFile(
|
||||
functionName,
|
||||
document.uri,
|
||||
);
|
||||
|
|
@ -125,24 +132,31 @@ export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
|||
|
||||
const optimizeCodeLens = new vscode.CodeLens(range);
|
||||
|
||||
optimizeCodeLens.command = {
|
||||
title: `optimize`,
|
||||
command: "codeflash.optimizeFunction",
|
||||
arguments: [document.uri, functionName],
|
||||
tooltip: `Optimize ${functionName}\n\nClick to analyze and optimize this function`,
|
||||
};
|
||||
const existingTask = this.globalState
|
||||
.get(GlobalStateKey.QueueTasks, [])
|
||||
?.find(
|
||||
(task) =>
|
||||
task.filepath === document.uri.fsPath &&
|
||||
task.functionName === functionName,
|
||||
);
|
||||
|
||||
optimizeCodeLens.command = this.buildCodelensCommand(
|
||||
functionName,
|
||||
document.uri,
|
||||
existingTask,
|
||||
);
|
||||
|
||||
codeLenses.push(optimizeCodeLens);
|
||||
}
|
||||
|
||||
this._logger.debug(
|
||||
this.logger.debug(
|
||||
`Provided ${codeLenses.length} CodeLenses for ${document.fileName}`,
|
||||
);
|
||||
return codeLenses;
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
this._logger.error(
|
||||
this.logger.error(
|
||||
"Error providing CodeLenses",
|
||||
error instanceof Error ? error : undefined,
|
||||
);
|
||||
|
|
@ -178,7 +192,7 @@ export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
|||
});
|
||||
} else {
|
||||
// Generic error - only show to developer/debug mode
|
||||
this._logger.warn(`CodeLens analysis failed: ${errorMessage}`);
|
||||
this.logger.warn(`CodeLens analysis failed: ${errorMessage}`);
|
||||
}
|
||||
|
||||
return [];
|
||||
|
|
@ -191,16 +205,79 @@ export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
|
|||
|
||||
public refresh(delay: number = 0): void {
|
||||
this._cachedSuggestions.clear();
|
||||
debounce(() => this._onDidChangeCodeLenses.fire(), delay, {
|
||||
immediate: delay === 0,
|
||||
});
|
||||
debounce(
|
||||
() => {
|
||||
this._onDidChangeCodeLenses.fire();
|
||||
},
|
||||
delay,
|
||||
{
|
||||
immediate: delay === 0,
|
||||
},
|
||||
)();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._logger.debug("Disposing CodeflashCodeLensProvider");
|
||||
this._cachedSuggestions.clear();
|
||||
vscode.Disposable.from(...this._disposables).dispose();
|
||||
public override dispose(): void {
|
||||
super.dispose();
|
||||
this.logger.debug("Disposing CodeflashCodeLensProvider");
|
||||
this._onDidChangeCodeLenses.dispose();
|
||||
this._logger.dispose();
|
||||
this.logger.dispose();
|
||||
}
|
||||
|
||||
private buildCodelensCommand(
|
||||
functionName: string,
|
||||
path: vscode.Uri,
|
||||
task: QueueTaskItem | undefined,
|
||||
): vscode.Command {
|
||||
let command: vscode.Command = {
|
||||
title: "Optimize",
|
||||
command: "codeflash.optimizeFunction",
|
||||
arguments: [path, functionName],
|
||||
tooltip: `Optimize ${functionName}\n\nClick to analyze and optimize this function`,
|
||||
};
|
||||
|
||||
if (task?.status === "completed") {
|
||||
const payload: ViewPatchMessage["payload"] = {
|
||||
id: task.id,
|
||||
functionName: task.functionName,
|
||||
patchFile: task.patchFile!,
|
||||
explanation: task.explanation || "",
|
||||
speedupStr: task.speedupStr || "",
|
||||
};
|
||||
|
||||
command = {
|
||||
title: "View Optimization",
|
||||
command: "codeflash.viewPatch",
|
||||
arguments: [payload],
|
||||
tooltip: `View optimization result for ${functionName}`,
|
||||
};
|
||||
} else if (task?.status === "optimizing") {
|
||||
const data: OutgoingWebviewMessage = {
|
||||
type: "setActiveSidebarTab",
|
||||
payload: {
|
||||
tab: "optimization",
|
||||
},
|
||||
};
|
||||
command = {
|
||||
title: "optimizing",
|
||||
command: "codeflash.sendMessage",
|
||||
arguments: [data],
|
||||
tooltip: `Optimization is currently running for function "${functionName}"`,
|
||||
};
|
||||
} else if (task?.status === "queued") {
|
||||
const data: OutgoingWebviewMessage = {
|
||||
type: "setActiveSidebarTab",
|
||||
payload: {
|
||||
tab: "tasks",
|
||||
},
|
||||
};
|
||||
command = {
|
||||
title: "queued",
|
||||
command: "codeflash.sendMessage",
|
||||
arguments: [data],
|
||||
tooltip: `Function "${functionName}" is queued for optimization`,
|
||||
};
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,44 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as cp from "child_process";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { marked } from "marked";
|
||||
import type { ViewPatchMessage } from "@codeflash/types";
|
||||
import type { StructuredPatch } from "diff";
|
||||
import { applyPatches, applyPatch } from "diff";
|
||||
import type { Logger } from "../utils";
|
||||
import type { GlobalState } from "../globalState";
|
||||
import { GlobalStateKey } from "../globalState";
|
||||
import { diff3Merge } from "../utils/diff.mjs";
|
||||
|
||||
const LINEBREAKS = /^.*(\r?\n|$)/gm;
|
||||
const markerSize = 7;
|
||||
const ourName = "codeflash";
|
||||
const theirName = "yours";
|
||||
|
||||
interface Diff3Result {
|
||||
ok?: string[];
|
||||
conflict?: {
|
||||
a: string[];
|
||||
o: string[];
|
||||
b: string[];
|
||||
};
|
||||
}
|
||||
export class GitPatchProvider {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private logger: any;
|
||||
private patchFile: string;
|
||||
private functionName: string;
|
||||
private explanation: string;
|
||||
private speedup: string;
|
||||
private merging: boolean = false;
|
||||
private _view: vscode.WebviewPanel | undefined;
|
||||
private onPatchApplied: () => void;
|
||||
|
||||
constructor(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
_logger: any,
|
||||
patch: ViewPatchMessage["payload"],
|
||||
private extensionUri: vscode.Uri,
|
||||
private globalState: GlobalState,
|
||||
private patch: ViewPatchMessage["payload"],
|
||||
private logger: Logger,
|
||||
_onPatchApplied?: () => void,
|
||||
) {
|
||||
this.logger = _logger;
|
||||
this.patchFile = patch.patchFile;
|
||||
this.speedup = patch.speedupStr || "";
|
||||
this.functionName = patch.functionName;
|
||||
|
|
@ -41,20 +58,29 @@ export class GitPatchProvider {
|
|||
|
||||
try {
|
||||
const patchContent = await fs.readFile(this.patchFile, "utf8");
|
||||
await this.showPatchWebview(patchContent, path.basename(this.patchFile));
|
||||
await this.showPatchWebview(
|
||||
patchContent,
|
||||
`Codeflash: ${this.functionName} (${this.speedup})`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to view patch: ${error}`);
|
||||
vscode.window.showErrorMessage(`Failed to open patch file: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupAndExit() {
|
||||
this.onPatchApplied();
|
||||
this._view?.dispose();
|
||||
this._view = undefined;
|
||||
}
|
||||
|
||||
private async showPatchWebview(
|
||||
patchContent: string,
|
||||
fileName: string,
|
||||
patchName: string,
|
||||
): Promise<void> {
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
"patchViewer",
|
||||
`Patch: ${fileName}`,
|
||||
patchName,
|
||||
vscode.ViewColumn.One,
|
||||
{
|
||||
enableScripts: true,
|
||||
|
|
@ -62,11 +88,16 @@ export class GitPatchProvider {
|
|||
localResourceRoots: [],
|
||||
},
|
||||
);
|
||||
panel.iconPath = vscode.Uri.joinPath(
|
||||
this.extensionUri,
|
||||
"media",
|
||||
"codeflash.svg",
|
||||
);
|
||||
|
||||
const explanationMarkdown = await marked(this.explanation);
|
||||
const html = this.createWebviewHtml(
|
||||
patchContent,
|
||||
fileName,
|
||||
patchName,
|
||||
explanationMarkdown,
|
||||
);
|
||||
panel.webview.html = html;
|
||||
|
|
@ -82,47 +113,145 @@ export class GitPatchProvider {
|
|||
|
||||
this._view = panel;
|
||||
}
|
||||
private get workspaceRoot(): string {
|
||||
const root = this.globalState.get(GlobalStateKey.RootDir, "")!;
|
||||
return root;
|
||||
}
|
||||
|
||||
private applyPatch() {
|
||||
if (
|
||||
!vscode.workspace.workspaceFolders ||
|
||||
vscode.workspace.workspaceFolders.length === 0
|
||||
) {
|
||||
vscode.window.showErrorMessage("No workspace folder is open.");
|
||||
return;
|
||||
private async applyWithConflicts(
|
||||
patch: StructuredPatch,
|
||||
): Promise<Error | null> {
|
||||
this.logger.info("Applying patch with conflicts");
|
||||
|
||||
if (this.merging) {
|
||||
return new Error("Pending merge is already running");
|
||||
}
|
||||
this.merging = true;
|
||||
|
||||
const contextEntries = this.globalState.get(
|
||||
GlobalStateKey.ContextFiles,
|
||||
{},
|
||||
)![this.patch.id];
|
||||
if (!contextEntries) {
|
||||
return new Error("no context entries for task" + this.patch.id);
|
||||
}
|
||||
|
||||
let base = "";
|
||||
|
||||
const ancestorFile = this.removeFilesPrefixes(patch.oldFileName);
|
||||
for (const [worktreePath, content] of Object.entries(contextEntries)) {
|
||||
if (!worktreePath.includes(ancestorFile)) {
|
||||
continue;
|
||||
}
|
||||
base = content;
|
||||
break;
|
||||
}
|
||||
if (base === "") {
|
||||
return new Error("Couldn't find the target file in the context files");
|
||||
}
|
||||
|
||||
const filePath = path.join(this.workspaceRoot, ancestorFile);
|
||||
|
||||
const theirs = await fs.readFile(filePath, { encoding: "utf-8" });
|
||||
const ours = applyPatch(base, patch, { fuzzFactor: 99 });
|
||||
if (!ours) {
|
||||
return new Error("couldn't apply the optimization patch to the base");
|
||||
}
|
||||
|
||||
this.logger.info("Starting 3-way merge...");
|
||||
|
||||
// Run the merge operation asynchronously to avoid blocking the main thread
|
||||
const diffResult = await new Promise<Diff3Result[]>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const result = diff3Merge(
|
||||
ours.match(LINEBREAKS)!,
|
||||
base.match(LINEBREAKS)!,
|
||||
theirs.match(LINEBREAKS)!,
|
||||
{},
|
||||
);
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
this.logger.info("3-way merge completed");
|
||||
|
||||
// Here we note whether there are conflicts and format the results
|
||||
let mergedText = "";
|
||||
let hasConflict = false;
|
||||
for (const item of diffResult) {
|
||||
if (item.ok) {
|
||||
mergedText += item.ok.join("");
|
||||
}
|
||||
|
||||
if (item.conflict) {
|
||||
hasConflict = true;
|
||||
mergedText += `<${"<".repeat(markerSize - 1)} ${ourName}\n`;
|
||||
mergedText += item.conflict.a.join("") + "\n";
|
||||
mergedText += `=${"=".repeat(markerSize - 1)}\n`;
|
||||
mergedText += item.conflict.b.join("") + "\n";
|
||||
mergedText += `>${">".repeat(markerSize - 1)} ${theirName}\n`;
|
||||
}
|
||||
}
|
||||
this.merging = false;
|
||||
|
||||
if (hasConflict) {
|
||||
await fs.writeFile(filePath, mergedText, { encoding: "utf-8" });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private removeFilesPrefixes = (path: string): string => {
|
||||
return path.replace("a/", "").replace("b/", "");
|
||||
};
|
||||
private async applyPatch() {
|
||||
const patchPath = this.patchFile;
|
||||
|
||||
const workspacePath = vscode.workspace.workspaceFolders[0]?.uri.fsPath;
|
||||
|
||||
const cmd = `git apply -3 --ignore-space-change --ignore-whitespace < ${JSON.stringify(patchPath)}`;
|
||||
|
||||
cp.exec(cmd, { cwd: workspacePath }, (err, _stdout, stderr) => {
|
||||
if (err) {
|
||||
const msg = stderr || err.message;
|
||||
const extraMsg = msg
|
||||
.toLocaleLowerCase()
|
||||
.includes("does not match index")
|
||||
? "Please try to commit or stash your changes and try again"
|
||||
: "";
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to apply patch: ${msg.trim()}, ${extraMsg}`,
|
||||
const patchContent = await fs.readFile(patchPath, { encoding: "utf-8" });
|
||||
applyPatches(patchContent, {
|
||||
loadFile: (index, callback) => {
|
||||
const filePath = this.removeFilesPrefixes(
|
||||
path.join(this.workspaceRoot, index.newFileName),
|
||||
);
|
||||
const appliedWithConflicts = msg
|
||||
.toLocaleLowerCase()
|
||||
.includes("conflicts");
|
||||
if (appliedWithConflicts) {
|
||||
vscode.window.showInformationMessage(
|
||||
"Optimization applied, but with conflicts.",
|
||||
|
||||
fs.readFile(filePath, { encoding: "utf-8" })
|
||||
.then((content) => callback(null, content))
|
||||
.catch((err) => callback(err, ""));
|
||||
},
|
||||
complete: (err) => {
|
||||
if (err) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to apply patch: ${err.message}`,
|
||||
);
|
||||
this.onPatchApplied();
|
||||
this._view && this._view.dispose();
|
||||
}
|
||||
},
|
||||
patched: (index, content) => {
|
||||
if (content) {
|
||||
const ancestorFile = this.removeFilesPrefixes(index.oldFileName);
|
||||
const filePath = path.join(this.workspaceRoot, ancestorFile);
|
||||
fs.writeFile(filePath, content, { encoding: "utf-8" }).then(() =>
|
||||
this.cleanupAndExit(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// apply with conflicts
|
||||
this.applyWithConflicts(index).then((err) => {
|
||||
// Merge operation completed
|
||||
if (err) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to apply patch: ${err.message}`,
|
||||
);
|
||||
} else {
|
||||
this.cleanupAndExit();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
vscode.window.showInformationMessage("Patch applied successfully.");
|
||||
this.onPatchApplied();
|
||||
this._view && this._view.dispose();
|
||||
},
|
||||
fuzzFactor: 99,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -304,7 +433,7 @@ export class GitPatchProvider {
|
|||
.explanation-section {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background-color: var(--vscode-editor-inactiveSelectionBackground, rgba(255, 255, 255, 0.04));
|
||||
background-color: var(--vscode-editor-background);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
|
@ -370,6 +499,7 @@ export class GitPatchProvider {
|
|||
</div>
|
||||
|
||||
<div class="content-area">
|
||||
<div id="myDiffElement"></div>
|
||||
${
|
||||
explanationHtml
|
||||
? `
|
||||
|
|
@ -382,7 +512,6 @@ export class GitPatchProvider {
|
|||
: ""
|
||||
}
|
||||
|
||||
<div id="myDiffElement"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
743
js/VSC-Extension/src/providers/InitWebviewProvider.ts
Normal file
743
js/VSC-Extension/src/providers/InitWebviewProvider.ts
Normal file
|
|
@ -0,0 +1,743 @@
|
|||
import type {
|
||||
CancellationToken,
|
||||
WebviewView,
|
||||
WebviewViewResolveContext,
|
||||
} from "vscode";
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import { generateNonce, getStaticMeta } from "../utils/staticUris";
|
||||
import { Disposable } from "../utils/dispose";
|
||||
import type { InitService } from "../services/initService";
|
||||
import { Logger } from "../utils";
|
||||
import { SIDEBAR_BUNDLE_DIR } from "../constants";
|
||||
import type { StepError } from "../boot/baseStep";
|
||||
|
||||
export type StepInfo = {
|
||||
status: "completed" | "failed" | "idle";
|
||||
icon?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
error?: StepError;
|
||||
};
|
||||
|
||||
export class InitWebviewProvider
|
||||
extends Disposable
|
||||
implements vscode.WebviewViewProvider
|
||||
{
|
||||
private _view?: vscode.WebviewView | undefined;
|
||||
private stepsInfo: StepInfo[] = [];
|
||||
private logger: Logger = new Logger("InitWebviewProvider");
|
||||
private currentInitMessage: string = "Initializing";
|
||||
private pendingMessages: unknown[] = [];
|
||||
|
||||
constructor(
|
||||
private _extensionUri: vscode.Uri,
|
||||
private readonly initService: InitService,
|
||||
) {
|
||||
super();
|
||||
this._disposables.push(this.logger);
|
||||
|
||||
// keep them in the constructor, don't put them in the resolveWebviewView as they are mendatory for the initService to work properly, and the view might not be resolved right away.
|
||||
this.initService.setUpdateInitMessage((m) => this.updateInitMessage(m));
|
||||
this.initService.setUpdateStep((n, u) => this.updateStep(n, u));
|
||||
this.initService.setAddSteps((...s) => this.addNewSteps(...s));
|
||||
this.initService.setRenderSteps(() => this.renderSteps());
|
||||
}
|
||||
|
||||
private updateInitMessage(message: string) {
|
||||
this.currentInitMessage = message;
|
||||
const msg = { command: "updateInitMessage", message };
|
||||
if (this._view) {
|
||||
this._view.webview.postMessage(msg);
|
||||
} else {
|
||||
this.pendingMessages.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private addNewSteps(...steps: StepInfo[]) {
|
||||
this.stepsInfo.push(...steps);
|
||||
}
|
||||
|
||||
private updateStep(number: number, update: Partial<StepInfo>) {
|
||||
const index = number - 1;
|
||||
const current = Object.assign({}, this.stepsInfo[index]);
|
||||
this.stepsInfo[index] = { ...current, ...update };
|
||||
}
|
||||
|
||||
private renderSteps() {
|
||||
const msg = {
|
||||
command: "renderSteps",
|
||||
steps: this.stepsInfo,
|
||||
};
|
||||
if (this._view) {
|
||||
this._view.webview.postMessage(msg);
|
||||
} else {
|
||||
this.pendingMessages.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
resolveWebviewView(
|
||||
webviewView: WebviewView,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_context: WebviewViewResolveContext,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_token: CancellationToken,
|
||||
): Thenable<void> | void {
|
||||
this._view = webviewView;
|
||||
this._view.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [
|
||||
vscode.Uri.joinPath(this._extensionUri, "media"),
|
||||
vscode.Uri.joinPath(this._extensionUri, SIDEBAR_BUNDLE_DIR, "assets"),
|
||||
],
|
||||
};
|
||||
|
||||
const nonce = generateNonce();
|
||||
const metaTags = getStaticMeta(
|
||||
this._view.webview,
|
||||
this._extensionUri,
|
||||
nonce,
|
||||
);
|
||||
this._view.webview.html = this.getHtmlContent(metaTags, nonce);
|
||||
|
||||
const listener = this._view.webview.onDidReceiveMessage((message) => {
|
||||
if (message.command === "restartExtension") {
|
||||
vscode.commands.executeCommand("codeflash.reloadExtension");
|
||||
} else if (message.command === "vscodeCommand") {
|
||||
vscode.commands.executeCommand(message.cmd);
|
||||
}
|
||||
});
|
||||
this._disposables.push(listener);
|
||||
|
||||
if (this.pendingMessages.length > 0) {
|
||||
this.pendingMessages.forEach((msg) => {
|
||||
this._view?.webview?.postMessage(msg);
|
||||
});
|
||||
this.pendingMessages = [];
|
||||
}
|
||||
}
|
||||
private getHtmlContent(metaTags: string, nonce: string): string {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Codeflash</title>
|
||||
${metaTags}
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:root {
|
||||
--codeflash-gradient-bg: linear-gradient(135deg, #ffc043, #685123);
|
||||
--step-gap: 4rem
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--vscode-font-family);
|
||||
font-size: var(--vscode-font-size);
|
||||
background: var(--vscode-sideBar-background);
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
animation: fadeIn 0.6s ease;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 0 auto 16px;
|
||||
padding: 0.6rem;
|
||||
background-image: var(--codeflash-gradient-bg);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logo::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: block;
|
||||
inset: -1px;
|
||||
background-image: var(--codeflash-gradient-bg);
|
||||
border-radius: 12px;
|
||||
opacity: 0.2;
|
||||
filter: blur(12px);
|
||||
z-index: -1;
|
||||
animation: pulse 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.logo .codicon {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading-wrapper {
|
||||
widht: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.wave-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.wave-bar {
|
||||
background: var(--vscode-foreground);
|
||||
border-radius: 50px;
|
||||
width: 4px; /* md uses w-0.5 => ~2px, lg => 4px */
|
||||
height: 30px;
|
||||
animation: wave-bars 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes wave-bars {
|
||||
0%,
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scaleY(0.6);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#init-message {
|
||||
font-size: 1rem;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Timeline */
|
||||
.timeline {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* flex: 1; */
|
||||
gap: var(--step-gap);
|
||||
}
|
||||
|
||||
.step {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
padding-left: 40px; /* Space for indicator */
|
||||
opacity: 0;
|
||||
animation: slideUp 0.5s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.step:nth-child(1) {
|
||||
animation-delay: 0.05s;
|
||||
}
|
||||
.step:nth-child(2) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.step:nth-child(3) {
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
.step:nth-child(4) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.step:nth-child(5) {
|
||||
animation-delay: 0.25s;
|
||||
}
|
||||
|
||||
/* Step Indicator */
|
||||
.step-indicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--vscode-input-border);
|
||||
background: var(--vscode-editor-background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.step-indicator .codicon {
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.step.completed .step-indicator {
|
||||
border-color: #10b981;
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.step.completed .step-indicator .codicon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step.failed .step-indicator {
|
||||
border-color: var(--vscode-errorForeground);
|
||||
background: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.step.failed .step-indicator .codicon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step.in-progress .step-indicator {
|
||||
border-color: var(--vscode-button-background);
|
||||
}
|
||||
|
||||
.step.in-progress .step-indicator .codicon {
|
||||
color: var(--vscode-button-background);
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
/* Connector Line */
|
||||
.step:not(:last-child)::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
top: 4px;
|
||||
bottom: calc(var(--step-gap) * -1);
|
||||
width: 2px;
|
||||
background: var(--vscode-editor-background);
|
||||
}
|
||||
|
||||
.step.completed:not(:last-child)::before {
|
||||
background: #10b981;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* Step Content */
|
||||
.step-content {
|
||||
min-height: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: var(--vscode-foreground);
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.step.idle .step-title {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.step-description {
|
||||
font-size: .9rem;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
line-height: 1.5;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* Error & Shell */
|
||||
.step-error {
|
||||
margin-top: 8px;
|
||||
padding: 12px;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid var(--vscode-errorForeground);
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-errorForeground);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.shell-command-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
background: var(--vscode-input-background);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
margin-top: 8px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-family: "SF Mono", monospace;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.shell-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.dollarSign::before {
|
||||
content: "$";
|
||||
margin-right: 1rem;
|
||||
color: var(--vscode-button-background);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--vscode-foreground);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
margin-left: 8px;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
opacity: 1;
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
}
|
||||
|
||||
|
||||
.step-action {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.step-action-btn {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
border-radius: 12px;
|
||||
padding: 0.3rem 1rem;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s ease;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.step-action-btn:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
|
||||
.step-action-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
.progress {
|
||||
margin-top: 48px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.4s ease;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--vscode-input-border);
|
||||
border-radius: 2px;
|
||||
margin-bottom: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background-image: var(--codeflash-gradient-bg);
|
||||
border-radius: 2px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
.header {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="logo">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
viewBox="0 0 48.1 32"
|
||||
>
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: black;
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
class="st0"
|
||||
d="M31.8.3h-9.4L5.6,16.9h9.4L0,31.7h10.5L31.3,10.3h-9.6L31.8.3Z"
|
||||
/>
|
||||
<path class="st0" d="M34.6.3l-5.9,6.1h13.5L48,.3h-13.4Z" />
|
||||
<path class="st0" d="M34.3,10.3l-5.9,6h13.5l5.8-6.1h-13.4Z" />
|
||||
<path class="st0" d="M26.9,18.6l-5.9,6.1h13.5l5.8-6.1h-13.4Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1>Codeflash</h1>
|
||||
</div>
|
||||
|
||||
<div class="loading-wrapper" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p id="init-message">${this.currentInitMessage}</p>
|
||||
<div class="wave-wrapper">
|
||||
${[...Array(5)].map((_, i) => `<div class="wave-bar" style="animation-delay:${i * 100}ms" ></div>`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline" id="steps-container"></div>
|
||||
<div class="progress" id="progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
<span id="progress-text">0 of 0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${nonce}">
|
||||
const vscode = acquireVsCodeApi();
|
||||
const loadingWrapper = document.querySelector('.loading-wrapper');
|
||||
const initMessageElement = document.getElementById('init-message');
|
||||
const stepsContainer = document.getElementById('steps-container');
|
||||
const restartExtensionBtn = document.getElementById('restart-extension-btn');
|
||||
const progressFill = document.getElementById('progress-fill');
|
||||
const progressText = document.getElementById('progress-text');
|
||||
|
||||
if (restartExtensionBtn) {
|
||||
restartExtensionBtn.addEventListener('click', () => {
|
||||
vscode.postMessage({ command: 'restartExtension' });
|
||||
restartExtensionBtn.disabled = true;
|
||||
restartExtensionBtn.innerText = 'Restarting...';
|
||||
});
|
||||
}
|
||||
|
||||
function updateProgress(steps) {
|
||||
const completed = steps.filter((s) => s.status === "completed").length;
|
||||
const total = steps.length;
|
||||
const percentage = (completed / total) * 100;
|
||||
|
||||
progressFill.style.width = percentage + '%';
|
||||
progressText.textContent = completed + " of " + total;
|
||||
progress.classList.add("active");
|
||||
}
|
||||
|
||||
function renderStepsUI(steps) {
|
||||
stepsContainer.innerHTML = "";
|
||||
|
||||
steps.forEach((step) => {
|
||||
console.log({step})
|
||||
const {
|
||||
message,
|
||||
_details: details,
|
||||
_helperCmdText: helperCmdText,
|
||||
_helperCmd: helperCmd,
|
||||
_vscodeActionCommand: vscodeActionCommand } = step.error || {};
|
||||
|
||||
const stepDiv = document.createElement("div");
|
||||
stepDiv.className = "step " + step.status;
|
||||
|
||||
const indicator = document.createElement("div");
|
||||
indicator.className = "step-indicator";
|
||||
|
||||
// const icon = document.createElement("span");
|
||||
// icon.className = "codicon codicon-" + step.icon;
|
||||
// indicator.appendChild(icon);
|
||||
|
||||
const card = document.createElement("div");
|
||||
card.className = "step-card";
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "step-header";
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.className = "step-title";
|
||||
title.textContent = step.title;
|
||||
header.appendChild(title);
|
||||
card.appendChild(header);
|
||||
|
||||
|
||||
if (message) {
|
||||
const error = document.createElement("div");
|
||||
error.className = "step-error";
|
||||
error.textContent = message;
|
||||
card.appendChild(error);
|
||||
}
|
||||
|
||||
|
||||
if (details) {
|
||||
const description = document.createElement("div");
|
||||
description.className = "step-description";
|
||||
description.innerHTML = details;
|
||||
card.appendChild(description);
|
||||
}
|
||||
|
||||
if (helperCmd) {
|
||||
if (helperCmdText) {
|
||||
const cmdTextElem = document.createElement("p")
|
||||
cmdTextElem.className = "cmd-text";
|
||||
cmdTextElem.textContent = helperCmdText;
|
||||
card.appendChild(cmdTextElem);
|
||||
}
|
||||
|
||||
const shell = document.createElement("div");
|
||||
shell.className = "shell-command-container";
|
||||
// wrapper will have the dollar sign and the command itself
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "shell-wrapper";
|
||||
|
||||
const dollarSign = document.createElement("span");
|
||||
dollarSign.className = "dollarSign";
|
||||
wrapper.appendChild(dollarSign);
|
||||
|
||||
const shellCmdElem = document.createElement("div");
|
||||
shellCmdElem.className = "shell-command";
|
||||
shellCmdElem.textContent = helperCmd;
|
||||
wrapper.appendChild(shellCmdElem);
|
||||
|
||||
|
||||
const copyBtn = document.createElement("button");
|
||||
copyBtn.className = "copy-btn";
|
||||
const copyIcon = document.createElement("span");
|
||||
copyIcon.className = "codicon codicon-copy";
|
||||
copyBtn.appendChild(copyIcon);
|
||||
copyBtn.addEventListener("click", () => {
|
||||
navigator.clipboard.writeText(helperCmd);
|
||||
copyIcon.className = "codicon codicon-check";
|
||||
setTimeout(() => {
|
||||
copyIcon.className = "codicon codicon-copy";
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
|
||||
shell.appendChild(wrapper);
|
||||
shell.appendChild(copyBtn);
|
||||
|
||||
card.appendChild(shell);
|
||||
}
|
||||
|
||||
if (vscodeActionCommand) {
|
||||
const {title, btnText, command} = vscodeActionCommand
|
||||
if (title) {
|
||||
const detailsElem = document.createElement("p")
|
||||
detailsElem.className = "step-action"
|
||||
detailsElem.textContent = title
|
||||
card.appendChild(detailsElem)
|
||||
}
|
||||
|
||||
const actionBtn = document.createElement("button")
|
||||
actionBtn.textContent = btnText
|
||||
actionBtn.className = "step-action-btn"
|
||||
actionBtn.addEventListener("click", () => {
|
||||
vscode.postMessage({ command: "vscodeCommand", cmd: command })
|
||||
})
|
||||
card.appendChild(actionBtn)
|
||||
}
|
||||
|
||||
stepDiv.appendChild(indicator);
|
||||
stepDiv.appendChild(card);
|
||||
stepsContainer.appendChild(stepDiv);
|
||||
});
|
||||
updateProgress(steps);
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const outgoingMessage = event.data;
|
||||
if (outgoingMessage.command === "updateInitMessage") {
|
||||
initMessageElement.textContent = outgoingMessage.message;
|
||||
} else if (outgoingMessage.command === "renderSteps") {
|
||||
loadingWrapper.style.display = 'none';
|
||||
renderStepsUI(outgoingMessage.steps);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
super.dispose();
|
||||
this._view = undefined;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,17 +26,19 @@ import type {
|
|||
RestoreStateMessage,
|
||||
SidebarStatus,
|
||||
UpdateStateMessage,
|
||||
ViewPatchMessage,
|
||||
} from "@codeflash/types";
|
||||
import { isTaskRunning, canRetryTaskInQueue } from "@codeflash/shared";
|
||||
import type { GlobalState } from "../globalState";
|
||||
import type { ContextFiles, GlobalState } from "../globalState";
|
||||
import { GlobalStateKey } from "../globalState";
|
||||
import { Disposable } from "../utils/dispose";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
export class SidebarProvider
|
||||
extends Disposable
|
||||
implements vscode.WebviewViewProvider
|
||||
{
|
||||
private _view?: vscode.WebviewView;
|
||||
private _view?: vscode.WebviewView | undefined;
|
||||
private readonly _logger: Logger;
|
||||
private _currentFocusUri: vscode.Uri | null = null;
|
||||
private _currentFunctionCount = 0;
|
||||
|
|
@ -51,6 +53,7 @@ export class SidebarProvider
|
|||
private readonly _navigationService: NavigationService,
|
||||
private readonly optimizationEventEmitter: vscode.EventEmitter<LogEntry>,
|
||||
private readonly globalState: GlobalState,
|
||||
private readonly refreshCodelens: () => void,
|
||||
) {
|
||||
super();
|
||||
this._logger = new Logger("Codeflash Sidebar");
|
||||
|
|
@ -110,29 +113,6 @@ export class SidebarProvider
|
|||
this.updateWebviewState("apiKeyError", "Invalid API key.", null, null);
|
||||
}
|
||||
|
||||
async checkApiKey(): Promise<boolean> {
|
||||
if (!this._lspClient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await this._lspClient.sendRequest<OptimizationResponse>(
|
||||
LSP_COMMANDS.CHECK_API_KEY,
|
||||
{},
|
||||
);
|
||||
this._logger.debug(JSON.stringify({ checkapiKey: result }));
|
||||
|
||||
if (result.status === "error") {
|
||||
this._logger.info(`Invalid API key, message: ${result.message}`);
|
||||
return false;
|
||||
} else if (result.status === "success") {
|
||||
this.globalState.set(GlobalStateKey.UserID, result.user_id as string);
|
||||
this._logger.info(`user id: ${result.user_id}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private retryUntilLspRunning = async (
|
||||
callback: () => Promise<void>,
|
||||
maxRetries = 5,
|
||||
|
|
@ -152,15 +132,12 @@ export class SidebarProvider
|
|||
callback();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
private handleInitialState = async (): Promise<void> => {
|
||||
try {
|
||||
if (!this.globalState.get(GlobalStateKey.UserID)) {
|
||||
const validApiKey = await this.checkApiKey();
|
||||
if (!validApiKey) {
|
||||
this._logger.info("API key is invalid.");
|
||||
this.sendWebviewApiKeyError();
|
||||
return;
|
||||
}
|
||||
this.sendWebviewApiKeyError();
|
||||
return;
|
||||
}
|
||||
|
||||
// get the initial active file
|
||||
|
|
@ -190,48 +167,6 @@ export class SidebarProvider
|
|||
return null;
|
||||
}
|
||||
|
||||
private async loadExistingOptimizationFromLSP() {
|
||||
this._logger.debug("Loading previous successful optimizations");
|
||||
if (this.globalState.get(GlobalStateKey.ExistingOptimizationFetched)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await this._lspClient.sendRequest<{
|
||||
status: string;
|
||||
patches: {
|
||||
id: string;
|
||||
fto_name: string;
|
||||
file_path: string;
|
||||
explanation: string;
|
||||
speedup: number;
|
||||
patch_path: string;
|
||||
}[];
|
||||
}>(LSP_COMMANDS.RETRIEVE_SUCCESSFUL_OPTS, {});
|
||||
if (result.status === "success") {
|
||||
this._logger.debug(
|
||||
`found ${result.patches.length} optimizations, adding them to the UI...`,
|
||||
);
|
||||
for (const patch of result.patches) {
|
||||
// update the ui with these patches
|
||||
const taskId = randomUUID().toString();
|
||||
this.updateUIQueueTasks("add", {
|
||||
id: taskId,
|
||||
status: "completed",
|
||||
functionName: patch.fto_name,
|
||||
filepath: patch.file_path,
|
||||
description: "Optimization completed successfully",
|
||||
patch_id: patch.id,
|
||||
patch_file: patch.patch_path,
|
||||
explanation: patch.explanation,
|
||||
speedupStr: `Speedup: ${patch.speedup.toFixed(2)}x faster`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.globalState.set(GlobalStateKey.ExistingOptimizationFetched, true);
|
||||
}
|
||||
}
|
||||
|
||||
private handleLspStateChange = (e: {
|
||||
newState: LanguageClientState;
|
||||
}): void => {
|
||||
|
|
@ -262,9 +197,6 @@ export class SidebarProvider
|
|||
this._logger.debug("Webview reported ready. restoring previous state");
|
||||
this.retryUntilLspRunning(this.handleInitialState.bind(this));
|
||||
this.restorePrevStateRequest();
|
||||
this.retryUntilLspRunning(
|
||||
this.loadExistingOptimizationFromLSP.bind(this),
|
||||
);
|
||||
break;
|
||||
case "navigateToFunction":
|
||||
await this.handleNavigateToFunction(
|
||||
|
|
@ -285,36 +217,7 @@ export class SidebarProvider
|
|||
await this.handleApiKeyEnter(data.payload.apiKey);
|
||||
break;
|
||||
case "viewPatch":
|
||||
const onPatchApplied = async () => {
|
||||
const { id, patchId } = data.payload;
|
||||
const task = this.globalState
|
||||
.get(GlobalStateKey.QueueTasks, [])
|
||||
?.find((t) => t.id === id);
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
if (task.filepath) {
|
||||
this._navigationService.navigateToFunction(
|
||||
task.functionName,
|
||||
vscode.Uri.file(task.filepath),
|
||||
);
|
||||
}
|
||||
// send message to the lsp to cleanup the patch metadata
|
||||
await this._optimizationService.sendPatchCleanupMessage(patchId);
|
||||
this.updateUIQueueTasks("remove", { id });
|
||||
};
|
||||
if (this.openedPathches.has(data.payload.patchFile)) {
|
||||
this.openedPathches.get(data.payload.patchFile)?.showPatch();
|
||||
return;
|
||||
}
|
||||
|
||||
const newPatchProvider = new GitPatchProvider(
|
||||
this._logger,
|
||||
data.payload,
|
||||
onPatchApplied,
|
||||
);
|
||||
this.openedPathches.set(data.payload.patchFile, newPatchProvider);
|
||||
newPatchProvider.showPatch();
|
||||
this.handleViewPatch(data.payload);
|
||||
break;
|
||||
case "deleteTask":
|
||||
this.handleDeleteTask(data.payload.id);
|
||||
|
|
@ -348,6 +251,47 @@ export class SidebarProvider
|
|||
}
|
||||
};
|
||||
|
||||
async handleViewPatch(payload: ViewPatchMessage["payload"]): Promise<void> {
|
||||
const { id, patchFile } = payload;
|
||||
if (this.openedPathches.has(patchFile)) {
|
||||
this.openedPathches.get(patchFile)?.showPatch();
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const onPatchApplied = async () => {
|
||||
const task = this.globalState
|
||||
.get(GlobalStateKey.QueueTasks, [])
|
||||
?.find((t) => t.id === id);
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
if (task.filepath) {
|
||||
this._navigationService.navigateToFunction(
|
||||
task.functionName,
|
||||
vscode.Uri.file(task.filepath),
|
||||
);
|
||||
}
|
||||
const newContextFiles = this.globalState.get(
|
||||
GlobalStateKey.ContextFiles,
|
||||
{},
|
||||
)!;
|
||||
delete newContextFiles[id];
|
||||
this.globalState.set(GlobalStateKey.ContextFiles, newContextFiles);
|
||||
this.updateUIQueueTasks("remove", { id });
|
||||
};
|
||||
|
||||
const newPatchProvider = new GitPatchProvider(
|
||||
this._extensionUri,
|
||||
this.globalState,
|
||||
payload,
|
||||
this._logger,
|
||||
onPatchApplied,
|
||||
);
|
||||
this.openedPathches.set(patchFile, newPatchProvider);
|
||||
await newPatchProvider.showPatch();
|
||||
}
|
||||
|
||||
private async requestFunctionsInFile(
|
||||
filePath: string,
|
||||
functions?: string[],
|
||||
|
|
@ -482,11 +426,9 @@ export class SidebarProvider
|
|||
}
|
||||
|
||||
private restorePrevStateRequest(): void {
|
||||
// @ts-ignore
|
||||
const clone: any = Object.assign({}, this.globalState._value);
|
||||
const message: RestoreStateMessage = {
|
||||
type: "restoreStateFromCache",
|
||||
state: clone,
|
||||
state: this.globalState.getStateForWebview(),
|
||||
};
|
||||
this.sendMessage(message);
|
||||
}
|
||||
|
|
@ -601,12 +543,9 @@ export class SidebarProvider
|
|||
|
||||
if (existingTask) {
|
||||
if (!canRetryTaskInQueue(existingTask)) {
|
||||
this._logger.debug(
|
||||
`Optimization for '${functionName}' in '${filePathStr}' is already ${existingTask.status}`,
|
||||
);
|
||||
vscode.window.showInformationMessage(
|
||||
`Function '${functionName}' is in queue for optimization with status (${existingTask.status})`,
|
||||
);
|
||||
const msg = `Optimization for '${functionName}' in '${filePathStr}' is already ${existingTask.status}`;
|
||||
this._logger.debug(msg);
|
||||
vscode.window.showInformationMessage(msg);
|
||||
return false;
|
||||
} else {
|
||||
this._optimizationService.abortTask(existingTask.id);
|
||||
|
|
@ -672,7 +611,7 @@ export class SidebarProvider
|
|||
this._logger.debug(
|
||||
`Starting optimization process for function '${functionName}' via sidebar`,
|
||||
);
|
||||
|
||||
this.refreshCodelens();
|
||||
const initResult =
|
||||
await this._optimizationService.initializeFunctionOptimization(
|
||||
functionName,
|
||||
|
|
@ -680,9 +619,12 @@ export class SidebarProvider
|
|||
);
|
||||
|
||||
if (initResult.status === "error") {
|
||||
const errorMsg = initResult.message || "";
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to initialize optimization ${errorMsg ? " :" + errorMsg : ""}`,
|
||||
);
|
||||
// remove the task from the queue since it failed to initialize
|
||||
this.updateUIQueueTasks("remove", { id: taskId });
|
||||
const errorMsg = initResult.message || "";
|
||||
if (errorMsg.toLowerCase().includes("not found")) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Function '${functionName}' not found in file '${filePathStr}, re-analyzing the file...`,
|
||||
|
|
@ -693,6 +635,10 @@ export class SidebarProvider
|
|||
return false;
|
||||
}
|
||||
|
||||
const filesInContext: string[] = initResult.files_inside_context || [];
|
||||
// store the initial content of each file in the optimization context to act as a common ancestor when performing 3-way merge later if we get a successful optimization
|
||||
this.perserveContextFilesSnapshot(id, filesInContext);
|
||||
|
||||
this.globalState.set(GlobalStateKey.Running, true);
|
||||
this.updateUIQueueTasks("update", {
|
||||
id,
|
||||
|
|
@ -706,13 +652,26 @@ export class SidebarProvider
|
|||
vscode.window.showInformationMessage(
|
||||
`Optimization completed successfully for function '${functionName}'`,
|
||||
);
|
||||
this.updateUIQueueTasks("update", {
|
||||
id,
|
||||
status: "completed",
|
||||
patch_file: result.patch_file!,
|
||||
const resultRes = {
|
||||
patchFile: result.patch_file!,
|
||||
explanation: result.explanation!,
|
||||
speedupStr: result.speedup || "",
|
||||
};
|
||||
const task = this.updateUIQueueTasks("update", {
|
||||
id,
|
||||
status: "completed",
|
||||
...resultRes,
|
||||
});
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
// show the patch immediately after the task is completed
|
||||
this.handleViewPatch({
|
||||
id: task.id,
|
||||
functionName: task.functionName,
|
||||
...resultRes,
|
||||
});
|
||||
this.refreshCodelens();
|
||||
},
|
||||
onTaskError: (error, id) => {
|
||||
this.globalState.set(GlobalStateKey.Running, false);
|
||||
|
|
@ -764,28 +723,48 @@ export class SidebarProvider
|
|||
return true;
|
||||
}
|
||||
|
||||
// Important: the files array here are paths of worktree, so make sure the worktree still exist before calling this function
|
||||
private perserveContextFilesSnapshot(
|
||||
taskId: string,
|
||||
filesInContext: string[],
|
||||
): void {
|
||||
const resolvedContextFiles: ContextFiles = {};
|
||||
for (const file of filesInContext) {
|
||||
const content = readFileSync(file, "utf-8");
|
||||
resolvedContextFiles[file] = content;
|
||||
}
|
||||
this.globalState.set(GlobalStateKey.ContextFiles, {
|
||||
...this.globalState.get(GlobalStateKey.ContextFiles, {}),
|
||||
[taskId]: resolvedContextFiles,
|
||||
});
|
||||
}
|
||||
|
||||
private updateUIQueueTasks(
|
||||
mode: "add" | "remove" | "update",
|
||||
task: Partial<QueueTaskItem>,
|
||||
) {
|
||||
): QueueTaskItem | null {
|
||||
const existingTasks =
|
||||
this.globalState.get(GlobalStateKey.QueueTasks, []) || [];
|
||||
|
||||
this._logger.debug(
|
||||
`queue state changed (${mode}) : ${JSON.stringify(task)}`,
|
||||
);
|
||||
let idx = -1;
|
||||
|
||||
if (mode === "add") {
|
||||
if (!task.id) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
task.logs = [];
|
||||
existingTasks.push(task as QueueTaskItem);
|
||||
idx = existingTasks.length - 1;
|
||||
} else if (mode === "update") {
|
||||
const index = existingTasks.findIndex((t) => t.id === task.id);
|
||||
if (index !== -1) {
|
||||
idx = index;
|
||||
const oldTask = existingTasks[index];
|
||||
if (!oldTask) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
existingTasks[index] = {
|
||||
...oldTask,
|
||||
|
|
@ -807,6 +786,7 @@ export class SidebarProvider
|
|||
tasks: existingTasks,
|
||||
},
|
||||
});
|
||||
return idx !== -1 ? existingTasks[idx]! : null;
|
||||
}
|
||||
|
||||
private handleVisibilityChange = async (): Promise<void> => {
|
||||
|
|
@ -946,7 +926,7 @@ export class SidebarProvider
|
|||
: null;
|
||||
}
|
||||
|
||||
private sendMessage(message: OutgoingWebviewMessage): void {
|
||||
sendMessage(message: OutgoingWebviewMessage): void {
|
||||
if (!this._view) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1089,7 +1069,7 @@ export class SidebarProvider
|
|||
}
|
||||
|
||||
override dispose(): void {
|
||||
this._view = undefined as any;
|
||||
this._view = undefined;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import * as vscode from "vscode";
|
||||
|
||||
export class WebviewSwitcherProvider implements vscode.WebviewViewProvider {
|
||||
private delegate: vscode.WebviewViewProvider;
|
||||
private current: vscode.WebviewViewProvider;
|
||||
private view?: vscode.WebviewView;
|
||||
private context?: vscode.WebviewViewResolveContext;
|
||||
|
||||
constructor(initial: vscode.WebviewViewProvider) {
|
||||
this.delegate = initial;
|
||||
this.current = initial;
|
||||
}
|
||||
|
||||
switchTo(provider: vscode.WebviewViewProvider) {
|
||||
this.delegate = provider;
|
||||
this.current = provider;
|
||||
if (this.view) {
|
||||
this.delegate.resolveWebviewView(
|
||||
this.current.resolveWebviewView(
|
||||
this.view,
|
||||
this.context!,
|
||||
new vscode.CancellationTokenSource().token,
|
||||
|
|
@ -27,9 +27,9 @@ export class WebviewSwitcherProvider implements vscode.WebviewViewProvider {
|
|||
) {
|
||||
this.view = view;
|
||||
this.context = context;
|
||||
if (!this.delegate) {
|
||||
throw new Error("No delegate provider set for WebviewSwitcherProvider");
|
||||
if (!this.current) {
|
||||
throw new Error("No provider set for WebviewSwitcherProvider");
|
||||
}
|
||||
this.delegate.resolveWebviewView(view, context, token);
|
||||
this.current.resolveWebviewView(view, context, token);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
181
js/VSC-Extension/src/services/initService.ts
Normal file
181
js/VSC-Extension/src/services/initService.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import type { CheckEnvironmentStepResult } from "../boot/checkEnvironmentStep";
|
||||
import { CheckEnvironmentStep } from "../boot/checkEnvironmentStep";
|
||||
import { Logger } from "../utils";
|
||||
import { Disposable } from "../utils/dispose";
|
||||
import * as vscode from "vscode";
|
||||
import type { StepInfo } from "../providers/InitWebviewProvider";
|
||||
import { StepError } from "../boot/baseStep";
|
||||
import type { BootCodeflashServerStepResult } from "../boot/bootCodeflashServer";
|
||||
import { BootCodeflashServerStep } from "../boot/bootCodeflashServer";
|
||||
import type { LogEntry } from "@codeflash/types";
|
||||
import { GlobalStateKey, type GlobalState } from "../globalState";
|
||||
import { CodeflashInitStep } from "../boot/codeflashInitStep";
|
||||
|
||||
type CompleteInitResult = Promise<null | {
|
||||
environmentResult: CheckEnvironmentStepResult;
|
||||
bootResult: BootCodeflashServerStepResult;
|
||||
}>;
|
||||
export class InitService extends Disposable {
|
||||
logger: Logger = new Logger("InitService");
|
||||
updateInitMessage?: (message: string) => void;
|
||||
updateStep?: (number: number, update: Partial<StepInfo>) => void;
|
||||
addSteps?: (...steps: StepInfo[]) => void;
|
||||
renderSteps?: () => void;
|
||||
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext,
|
||||
private readonly globalState: GlobalState,
|
||||
private readonly optimizationEventEmitter: vscode.EventEmitter<LogEntry>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public setUpdateInitMessage(updateInitMessage: (message: string) => void) {
|
||||
this.updateInitMessage = updateInitMessage;
|
||||
}
|
||||
public setRenderSteps(renderSteps: () => void) {
|
||||
this.renderSteps = renderSteps;
|
||||
}
|
||||
public setUpdateStep(
|
||||
updateStep: (number: number, update: Partial<StepInfo>) => void,
|
||||
) {
|
||||
this.updateStep = updateStep;
|
||||
}
|
||||
public setAddSteps(addSteps: (...steps: StepInfo[]) => void) {
|
||||
this.addSteps = addSteps;
|
||||
}
|
||||
|
||||
public async initWithLoading(maxRetries: number = 3): CompleteInitResult {
|
||||
return await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "Initializing Codeflash extension...",
|
||||
cancellable: false,
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
return this.init();
|
||||
} catch (error) {
|
||||
if (maxRetries > 0) {
|
||||
this.logger.warn(
|
||||
`InitService init failed, retrying... (${maxRetries} retries left)`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
return this.initWithLoading(maxRetries - 1);
|
||||
} else {
|
||||
this.logger.error(`InitService init failed: ${error}`);
|
||||
vscode.window.showErrorMessage(
|
||||
`Codeflash initialization failed: ${error}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async init(): CompleteInitResult {
|
||||
if (
|
||||
!this.addSteps ||
|
||||
!this.updateInitMessage ||
|
||||
!this.updateStep ||
|
||||
!this.renderSteps
|
||||
) {
|
||||
throw new Error("InitService not initialized yet");
|
||||
}
|
||||
|
||||
this.addSteps(
|
||||
{
|
||||
status: "idle",
|
||||
// icon: "vm",
|
||||
title: "Checking prerequisites",
|
||||
description: "",
|
||||
},
|
||||
{
|
||||
status: "idle",
|
||||
// icon: "symbol-event",
|
||||
title: "Starting Codeflash server",
|
||||
description: "",
|
||||
},
|
||||
{
|
||||
status: "idle",
|
||||
// icon: "code",
|
||||
title: "Initializing Codeflash",
|
||||
description: "",
|
||||
},
|
||||
);
|
||||
|
||||
const environmentStep = new CheckEnvironmentStep();
|
||||
this._disposables.push(environmentStep);
|
||||
|
||||
// Step 1: Check environment
|
||||
this.updateInitMessage(environmentStep.stepTitle);
|
||||
const environmentResult = await environmentStep.run();
|
||||
if (environmentResult instanceof StepError) {
|
||||
this.updateStep(1, {
|
||||
status: "failed",
|
||||
error: environmentResult,
|
||||
});
|
||||
this.renderSteps();
|
||||
return null;
|
||||
}
|
||||
this.logger.info(
|
||||
`using python interpreter: ${environmentResult.pythonPath}`,
|
||||
);
|
||||
this.updateStep(1, { status: "completed" });
|
||||
|
||||
// Step 2: Start server and initialize services
|
||||
const bootServerStep = new BootCodeflashServerStep(
|
||||
this.context,
|
||||
this.globalState,
|
||||
environmentResult.pythonPath,
|
||||
this.optimizationEventEmitter,
|
||||
);
|
||||
this._disposables.push(bootServerStep);
|
||||
this.updateInitMessage(bootServerStep.stepTitle);
|
||||
|
||||
const bootResult = await bootServerStep.run();
|
||||
|
||||
if (bootResult instanceof StepError) {
|
||||
this.updateStep(2, {
|
||||
status: "failed",
|
||||
error: bootResult,
|
||||
});
|
||||
this.renderSteps();
|
||||
return null;
|
||||
}
|
||||
this.updateStep(2, { status: "completed" });
|
||||
// Step 3: codeflash validation and initialization completed
|
||||
const lspClient = bootResult.lspService.getClient();
|
||||
const optimizationClient = bootResult.optimizationLspService.getClient();
|
||||
const finalizeStep = new CodeflashInitStep(
|
||||
lspClient,
|
||||
optimizationClient,
|
||||
this.globalState,
|
||||
);
|
||||
|
||||
this._disposables.push(finalizeStep);
|
||||
this.updateInitMessage(finalizeStep.stepTitle);
|
||||
|
||||
const finalizeResult = await finalizeStep.run();
|
||||
if (finalizeResult instanceof StepError) {
|
||||
this.updateStep(3, {
|
||||
status: "failed",
|
||||
error: finalizeResult,
|
||||
});
|
||||
this.renderSteps();
|
||||
return null;
|
||||
}
|
||||
this.updateStep(3, { status: "completed" });
|
||||
this.logger.info(
|
||||
">>> user id " + this.globalState.get(GlobalStateKey.UserID),
|
||||
);
|
||||
bootResult.codeLensProvider.refresh(500);
|
||||
// DONE
|
||||
|
||||
return {
|
||||
environmentResult,
|
||||
bootResult,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export interface OptimizationResult {
|
|||
success: boolean;
|
||||
error?: string;
|
||||
patch_file?: string;
|
||||
patch_id?: string;
|
||||
task_id?: string;
|
||||
explanation?: string;
|
||||
speedup?: string;
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ export class OptimizationService {
|
|||
|
||||
try {
|
||||
const result = await this.optimizeFunction(
|
||||
id,
|
||||
functionName,
|
||||
options.updateQueueTask,
|
||||
);
|
||||
|
|
@ -78,6 +79,7 @@ export class OptimizationService {
|
|||
}
|
||||
|
||||
private async optimizeFunction(
|
||||
taskId: string,
|
||||
functionName: string,
|
||||
updateTask: (payload: Partial<Omit<QueueTaskItem, "id">>) => void,
|
||||
): Promise<OptimizationResult> {
|
||||
|
|
@ -95,8 +97,10 @@ export class OptimizationService {
|
|||
try {
|
||||
updateTask({ description: "Performing function optimization" });
|
||||
// Step 2: Perform function optimization
|
||||
const optimizationResult =
|
||||
await this.performFunctionOptimization(functionName);
|
||||
const optimizationResult = await this.performFunctionOptimization(
|
||||
taskId,
|
||||
functionName,
|
||||
);
|
||||
|
||||
// Check if optimization failed due to threshold not being met or other reasons
|
||||
if (optimizationResult.status === "error") {
|
||||
|
|
@ -149,7 +153,7 @@ export class OptimizationService {
|
|||
success: true,
|
||||
patch_file: optimizationResult.patch_file as string,
|
||||
explanation: optimizationResult.explanation as string,
|
||||
patch_id: optimizationResult.patch_id as string,
|
||||
task_id: optimizationResult.task_id as string,
|
||||
speedup: optimizationResult.extra as string,
|
||||
};
|
||||
} catch (error) {
|
||||
|
|
@ -179,13 +183,12 @@ export class OptimizationService {
|
|||
},
|
||||
);
|
||||
|
||||
this.logger.info(
|
||||
`Initialization successful for ${functionName}. Response: ${JSON.stringify(result)}`,
|
||||
);
|
||||
this.logger.info(`Initialization Response: ${JSON.stringify(result)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
async performFunctionOptimization(
|
||||
taskId: string,
|
||||
functionName: string,
|
||||
): Promise<OptimizationResponse> {
|
||||
this.logger.info(`Performing optimization for function ${functionName}`);
|
||||
|
|
@ -193,7 +196,11 @@ export class OptimizationService {
|
|||
const result =
|
||||
await this.optimizationClient.sendRequest<OptimizationResponse>(
|
||||
LSP_COMMANDS.PERFORM_FUNCTION_OPTIMIZATION,
|
||||
{ textDocument: { uri: "" }, functionName: functionName },
|
||||
{
|
||||
textDocument: { uri: "" },
|
||||
functionName: functionName,
|
||||
task_id: taskId,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === "error") {
|
||||
|
|
@ -208,12 +215,12 @@ export class OptimizationService {
|
|||
return result;
|
||||
}
|
||||
|
||||
async sendPatchCleanupMessage(patchId: string): Promise<Boolean> {
|
||||
async sendPatchCleanupMessage(taskId: string): Promise<Boolean> {
|
||||
try {
|
||||
const result =
|
||||
await this.optimizationClient.sendRequest<OptimizationResponse>(
|
||||
LSP_COMMANDS.ON_PATCH_APPLIED,
|
||||
{ patch_id: patchId },
|
||||
{ task_id: taskId },
|
||||
);
|
||||
if (result.status === "success") {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import type {
|
|||
import { PYTHON_EXTENSION_ID } from "../constants";
|
||||
import type { ErrorWebviewConfig } from "../types";
|
||||
import { Logger } from "../utils";
|
||||
import { join } from "path";
|
||||
import { existsSync } from "fs";
|
||||
import { getRootWorkspaceFolder } from "../utils/rootWorkspace";
|
||||
|
||||
interface ExtensionDependencies {
|
||||
pythonPath?: string;
|
||||
|
|
@ -54,7 +57,6 @@ export class PythonService {
|
|||
}
|
||||
|
||||
const pythonPath = activeEnvironment.executable.uri.fsPath;
|
||||
this.logger.info(`Codeflash uses Python in the environment: ${pythonPath}`);
|
||||
|
||||
// check for codeflash lsp module
|
||||
// try {
|
||||
|
|
@ -89,7 +91,7 @@ export class PythonService {
|
|||
// }
|
||||
// const workspacePath = workspaceFolder.uri.fsPath;
|
||||
// for (const venv of possibleVenvs) {
|
||||
// const venvPath = path.join(workspacePath, venv, "bin", "python");
|
||||
// const venvPath = join(workspacePath, venv, "bin", "python");
|
||||
// try {
|
||||
// await vscode.workspace.fs.stat(vscode.Uri.file(venvPath));
|
||||
// return venvPath;
|
||||
|
|
@ -98,4 +100,18 @@ export class PythonService {
|
|||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
public getPackageManager(root?: string): "uv" | null {
|
||||
const workspaceFolder = getRootWorkspaceFolder();
|
||||
const finalRoot = root ?? workspaceFolder?.uri.fsPath;
|
||||
|
||||
if (!finalRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (existsSync(join(finalRoot, "uv.lock"))) {
|
||||
return "uv";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export interface OptimizationResponse {
|
|||
message?: string;
|
||||
optimization?: string; // the source code for the best candidate
|
||||
functions?: string[];
|
||||
files_inside_context: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,267 +0,0 @@
|
|||
import type {
|
||||
CancellationToken,
|
||||
WebviewView,
|
||||
WebviewViewResolveContext,
|
||||
} from "vscode";
|
||||
import type { ErrorWebviewConfig } from "../types";
|
||||
import * as vscode from "vscode";
|
||||
import { SIDEBAR_BUNDLE_DIR } from "../constants";
|
||||
import { generateNonce, getStaticMeta } from "./staticUris";
|
||||
import { marked } from "marked";
|
||||
export class ErrorWebview implements vscode.WebviewViewProvider {
|
||||
constructor(
|
||||
private config: ErrorWebviewConfig,
|
||||
private _extensionUri: vscode.Uri,
|
||||
) {}
|
||||
|
||||
resolveWebviewView(
|
||||
webviewView: WebviewView,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_context: WebviewViewResolveContext,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_token: CancellationToken,
|
||||
): Thenable<void> | void {
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [
|
||||
vscode.Uri.joinPath(this._extensionUri, SIDEBAR_BUNDLE_DIR, "assets"),
|
||||
],
|
||||
};
|
||||
|
||||
const nonce = generateNonce();
|
||||
const metaTags = getStaticMeta(
|
||||
webviewView.webview,
|
||||
this._extensionUri,
|
||||
nonce,
|
||||
);
|
||||
|
||||
webviewView.webview.html = this.getHtmlContent(metaTags, nonce);
|
||||
webviewView.webview.onDidReceiveMessage((message) => {
|
||||
if (message.command === "restartExtension") {
|
||||
vscode.commands.executeCommand("codeflash.reloadExtension");
|
||||
}
|
||||
if (this.config.actionButton && message.command === "actionButton") {
|
||||
this.config.actionButton.onClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getHtmlContent(metaTags: string, nonce: string): string {
|
||||
const detailsMarkdown = marked(this.config.details);
|
||||
const terminalCommandSection = this.config.terminalCommand
|
||||
? `
|
||||
<div class="terminal-section">
|
||||
<div class="terminal-command">
|
||||
<code>${this.config.terminalCommand}</code>
|
||||
<button id="copyBtn" class="copy-btn" title="Copy to clipboard">
|
||||
<span class="codicon codicon-copy"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: "";
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Codeflash Error</title>
|
||||
${metaTags}
|
||||
<style>
|
||||
body {
|
||||
font-family: var(--vscode-font-family);
|
||||
font-size: var(--vscode-font-size);
|
||||
background-color: var(--vscode-sideBar-background);
|
||||
color: var(--vscode-foreground);
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.icon > span {
|
||||
font-size: 40px !important;
|
||||
}
|
||||
|
||||
.icon.warning {
|
||||
color: var(--vscode-notificationsWarningIcon-foreground);
|
||||
}
|
||||
|
||||
.icon.error {
|
||||
color: var(--vscode-notificationsErrorIcon-foreground);
|
||||
}
|
||||
|
||||
.icon.info {
|
||||
color: var(--vscode-notificationsInfoIcon-foreground);
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--vscode-foreground);
|
||||
font-size: 17px !important;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
line-height: 1.5;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--vscode-textPreformat-foreground);
|
||||
background-color: var(--vscode-textPreformat-background);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.terminal-section {
|
||||
margin-top: 24px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.terminal-label {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 8px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.terminal-command {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--vscode-textPreformat-background);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
}
|
||||
|
||||
.terminal-command code {
|
||||
flex: 1;
|
||||
background: none;
|
||||
color: var(--vscode-textPreformat-foreground);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--vscode-foreground);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
margin-left: 8px;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
opacity: 1;
|
||||
background-color: var(--vscode-toolbar-hoverBackground);
|
||||
}
|
||||
|
||||
.execute-btn {
|
||||
background-color: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.execute-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.execute-btn:hover {
|
||||
background-color: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<p class="icon ${this.getIconClass()}">
|
||||
<span class="codicon codicon-${this.config.icon}"></span>
|
||||
</p>
|
||||
<h2 class="title">${this.config.title}</h2>
|
||||
<p>${detailsMarkdown}</p>
|
||||
${terminalCommandSection}
|
||||
|
||||
${this.config.actionButton ? `<button id="action-btn" class="execute-btn">${this.config.actionButton.text}</button>` : ""}
|
||||
<button id="restart-extension-btn" class="execute-btn">Restart Extension</button>
|
||||
</div>
|
||||
|
||||
<script nonce="${nonce}">
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
const actionBtn = document.getElementById('action-btn');
|
||||
if (actionBtn) {
|
||||
actionBtn.addEventListener('click', () => {
|
||||
vscode.postMessage({ command: 'actionButton' });
|
||||
});
|
||||
}
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener('click', () => {
|
||||
const command = '${this.config.terminalCommand}';
|
||||
navigator.clipboard.writeText(command).then(() => {
|
||||
copyBtn.innerHTML = '<span class="codicon codicon-check"></span>';
|
||||
setTimeout(() => {
|
||||
copyBtn.innerHTML = '<span class="codicon codicon-copy"></span>';
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const restartExtensionBtn = document.getElementById('restart-extension-btn');
|
||||
if (restartExtensionBtn) {
|
||||
restartExtensionBtn.addEventListener('click', () => {
|
||||
vscode.postMessage({ command: 'restartExtension' });
|
||||
restartExtensionBtn.disabled = true;
|
||||
restartExtensionBtn.innerText = 'Restarting...';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
private getIconClass(): string {
|
||||
switch (this.config.icon) {
|
||||
case "warning":
|
||||
return "warning";
|
||||
case "error":
|
||||
return "error";
|
||||
case "info":
|
||||
return "info";
|
||||
default:
|
||||
return "warning";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,246 +0,0 @@
|
|||
import { generateNonce } from "./staticUris";
|
||||
import type * as vscode from "vscode";
|
||||
export class LoadingWebview implements vscode.WebviewViewProvider {
|
||||
private webviewView?: vscode.WebviewView;
|
||||
|
||||
constructor() {}
|
||||
|
||||
resolveWebviewView(
|
||||
webviewView: vscode.WebviewView,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_context: vscode.WebviewViewResolveContext,
|
||||
): Thenable<void> | void {
|
||||
this.webviewView = webviewView;
|
||||
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
};
|
||||
webviewView.webview.html = this.getHtmlContent();
|
||||
}
|
||||
|
||||
private getHtmlContent(): string {
|
||||
const nonce = generateNonce();
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Security-Policy" content="
|
||||
default-src 'none';
|
||||
style-src 'unsafe-inline';
|
||||
img-src ${this.webviewView!.webview.cspSource} data:;
|
||||
script-src 'nonce-${nonce}';
|
||||
">
|
||||
<title>Codeflash Loading</title>
|
||||
<style>
|
||||
:root {
|
||||
--vscode-font-family: var(--vscode-font-family);
|
||||
--vscode-font-size: var(--vscode-font-size);
|
||||
--vscode-font-weight: var(--vscode-font-weight);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--vscode-font-family);
|
||||
font-size: var(--vscode-font-size);
|
||||
font-weight: var(--vscode-font-weight);
|
||||
color: var(--vscode-foreground);
|
||||
background-color: var(--vscode-sideBar-background);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
padding: 32px 16px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
position: relative;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
opacity: 0.9;
|
||||
filter: brightness(0) saturate(100%) invert(85%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%);
|
||||
}
|
||||
|
||||
.pulse-ring {
|
||||
position: absolute;
|
||||
border: 2px solid var(--vscode-progressBar-background);
|
||||
border-radius: 50%;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
animation: pulse 2s cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite;
|
||||
}
|
||||
|
||||
.pulse-ring:nth-child(2) {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--vscode-progressBar-background);
|
||||
border-top: 2px solid var(--vscode-progressBar-foreground);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: var(--vscode-progressBar-background);
|
||||
border-radius: 1px;
|
||||
overflow: hidden;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background-color: var(--vscode-progressBar-foreground);
|
||||
border-radius: 1px;
|
||||
animation: progress 3s ease-in-out infinite;
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
margin-top: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.4;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
0% {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
25% {
|
||||
transform: scaleX(0.3);
|
||||
}
|
||||
50% {
|
||||
transform: scaleX(0.6);
|
||||
}
|
||||
75% {
|
||||
transform: scaleX(0.8);
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark theme adjustments */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.logo {
|
||||
filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 300px) {
|
||||
.loading-container {
|
||||
padding: 16px 8px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.pulse-ring {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="loading-container">
|
||||
<div class="logo-container">
|
||||
<div class="pulse-ring"></div>
|
||||
<div class="pulse-ring"></div>
|
||||
<svg class="logo" id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 48.1 32">
|
||||
<!-- Generator: Adobe Illustrator 29.6.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 207) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #787878;
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="st0" d="M31.8.3h-9.4L5.6,16.9h9.4L0,31.7h10.5L31.3,10.3h-9.6L31.8.3Z"/>
|
||||
<path class="st0" d="M34.6.3l-5.9,6.1h13.5L48,.3h-13.4Z"/>
|
||||
<path class="st0" d="M34.3,10.3l-5.9,6h13.5l5.8-6.1h-13.4Z"/>
|
||||
<path class="st0" d="M26.9,18.6l-5.9,6.1h13.5l5.8-6.1h-13.4Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="loading-text">Initializing Codeflash</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${nonce}">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
}
|
||||
554
js/VSC-Extension/src/utils/diff.mjs
Normal file
554
js/VSC-Extension/src/utils/diff.mjs
Normal file
|
|
@ -0,0 +1,554 @@
|
|||
export {
|
||||
LCS,
|
||||
diffComm,
|
||||
diffIndices,
|
||||
diffPatch,
|
||||
diff3MergeRegions,
|
||||
diff3Merge,
|
||||
mergeDiff3,
|
||||
merge,
|
||||
mergeDigIn,
|
||||
patch,
|
||||
stripPatch,
|
||||
invertPatch
|
||||
};
|
||||
|
||||
|
||||
// Text diff algorithm following Hunt and McIlroy 1976.
|
||||
// J. W. Hunt and M. D. McIlroy, An algorithm for differential buffer
|
||||
// comparison, Bell Telephone Laboratories CSTR #41 (1976)
|
||||
// http://www.cs.dartmouth.edu/~doug/
|
||||
// https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
|
||||
//
|
||||
// Expects two arrays, finds longest common sequence
|
||||
function LCS(buffer1, buffer2) {
|
||||
|
||||
let equivalenceClasses = {};
|
||||
for (let j = 0; j < buffer2.length; j++) {
|
||||
const item = buffer2[j];
|
||||
if (equivalenceClasses[item]) {
|
||||
equivalenceClasses[item].push(j);
|
||||
} else {
|
||||
equivalenceClasses[item] = [j];
|
||||
}
|
||||
}
|
||||
|
||||
const NULLRESULT = { buffer1index: -1, buffer2index: -1, chain: null };
|
||||
let candidates = [NULLRESULT];
|
||||
|
||||
for (let i = 0; i < buffer1.length; i++) {
|
||||
const item = buffer1[i];
|
||||
const buffer2indices = equivalenceClasses[item] || [];
|
||||
let r = 0;
|
||||
let c = candidates[0];
|
||||
|
||||
for (let jx = 0; jx < buffer2indices.length; jx++) {
|
||||
const j = buffer2indices[jx];
|
||||
|
||||
let s;
|
||||
for (s = r; s < candidates.length; s++) {
|
||||
if ((candidates[s].buffer2index < j) && ((s === candidates.length - 1) || (candidates[s + 1].buffer2index > j))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s < candidates.length) {
|
||||
const newCandidate = { buffer1index: i, buffer2index: j, chain: candidates[s] };
|
||||
if (r === candidates.length) {
|
||||
candidates.push(c);
|
||||
} else {
|
||||
candidates[r] = c;
|
||||
}
|
||||
r = s + 1;
|
||||
c = newCandidate;
|
||||
if (r === candidates.length) {
|
||||
break; // no point in examining further (j)s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
candidates[r] = c;
|
||||
}
|
||||
|
||||
// At this point, we know the LCS: it's in the reverse of the
|
||||
// linked-list through .chain of candidates[candidates.length - 1].
|
||||
|
||||
return candidates[candidates.length - 1];
|
||||
}
|
||||
|
||||
|
||||
// We apply the LCS to build a 'comm'-style picture of the
|
||||
// differences between buffer1 and buffer2.
|
||||
function diffComm(buffer1, buffer2) {
|
||||
const lcs = LCS(buffer1, buffer2);
|
||||
let result = [];
|
||||
let tail1 = buffer1.length;
|
||||
let tail2 = buffer2.length;
|
||||
let common = {common: []};
|
||||
|
||||
function processCommon() {
|
||||
if (common.common.length) {
|
||||
common.common.reverse();
|
||||
result.push(common);
|
||||
common = {common: []};
|
||||
}
|
||||
}
|
||||
|
||||
for (let candidate = lcs; candidate !== null; candidate = candidate.chain) {
|
||||
let different = {buffer1: [], buffer2: []};
|
||||
|
||||
while (--tail1 > candidate.buffer1index) {
|
||||
different.buffer1.push(buffer1[tail1]);
|
||||
}
|
||||
|
||||
while (--tail2 > candidate.buffer2index) {
|
||||
different.buffer2.push(buffer2[tail2]);
|
||||
}
|
||||
|
||||
if (different.buffer1.length || different.buffer2.length) {
|
||||
processCommon();
|
||||
different.buffer1.reverse();
|
||||
different.buffer2.reverse();
|
||||
result.push(different);
|
||||
}
|
||||
|
||||
if (tail1 >= 0) {
|
||||
common.common.push(buffer1[tail1]);
|
||||
}
|
||||
}
|
||||
|
||||
processCommon();
|
||||
|
||||
result.reverse();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// We apply the LCS to give a simple representation of the
|
||||
// offsets and lengths of mismatched chunks in the input
|
||||
// buffers. This is used by diff3MergeRegions.
|
||||
function diffIndices(buffer1, buffer2) {
|
||||
const lcs = LCS(buffer1, buffer2);
|
||||
let result = [];
|
||||
let tail1 = buffer1.length;
|
||||
let tail2 = buffer2.length;
|
||||
|
||||
for (let candidate = lcs; candidate !== null; candidate = candidate.chain) {
|
||||
const mismatchLength1 = tail1 - candidate.buffer1index - 1;
|
||||
const mismatchLength2 = tail2 - candidate.buffer2index - 1;
|
||||
tail1 = candidate.buffer1index;
|
||||
tail2 = candidate.buffer2index;
|
||||
|
||||
if (mismatchLength1 || mismatchLength2) {
|
||||
result.push({
|
||||
buffer1: [tail1 + 1, mismatchLength1],
|
||||
buffer1Content: buffer1.slice(tail1 + 1, tail1 + 1 + mismatchLength1),
|
||||
buffer2: [tail2 + 1, mismatchLength2],
|
||||
buffer2Content: buffer2.slice(tail2 + 1, tail2 + 1 + mismatchLength2)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.reverse();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// We apply the LCS to build a JSON representation of a
|
||||
// diff(1)-style patch.
|
||||
function diffPatch(buffer1, buffer2) {
|
||||
const lcs = LCS(buffer1, buffer2);
|
||||
let result = [];
|
||||
let tail1 = buffer1.length;
|
||||
let tail2 = buffer2.length;
|
||||
|
||||
function chunkDescription(buffer, offset, length) {
|
||||
let chunk = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
chunk.push(buffer[offset + i]);
|
||||
}
|
||||
return {
|
||||
offset: offset,
|
||||
length: length,
|
||||
chunk: chunk
|
||||
};
|
||||
}
|
||||
|
||||
for (let candidate = lcs; candidate !== null; candidate = candidate.chain) {
|
||||
const mismatchLength1 = tail1 - candidate.buffer1index - 1;
|
||||
const mismatchLength2 = tail2 - candidate.buffer2index - 1;
|
||||
tail1 = candidate.buffer1index;
|
||||
tail2 = candidate.buffer2index;
|
||||
|
||||
if (mismatchLength1 || mismatchLength2) {
|
||||
result.push({
|
||||
buffer1: chunkDescription(buffer1, candidate.buffer1index + 1, mismatchLength1),
|
||||
buffer2: chunkDescription(buffer2, candidate.buffer2index + 1, mismatchLength2)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.reverse();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Given three buffers, A, O, and B, where both A and B are
|
||||
// independently derived from O, returns a fairly complicated
|
||||
// internal representation of merge decisions it's taken. The
|
||||
// interested reader may wish to consult
|
||||
//
|
||||
// Sanjeev Khanna, Keshav Kunal, and Benjamin C. Pierce.
|
||||
// 'A Formal Investigation of ' In Arvind and Prasad,
|
||||
// editors, Foundations of Software Technology and Theoretical
|
||||
// Computer Science (FSTTCS), December 2007.
|
||||
//
|
||||
// (http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf)
|
||||
//
|
||||
function diff3MergeRegions(a, o, b) {
|
||||
|
||||
// "hunks" are array subsets where `a` or `b` are different from `o`
|
||||
// https://www.gnu.org/software/diffutils/manual/html_node/diff3-Hunks.html
|
||||
let hunks = [];
|
||||
function addHunk(h, ab) {
|
||||
hunks.push({
|
||||
ab: ab,
|
||||
oStart: h.buffer1[0],
|
||||
oLength: h.buffer1[1], // length of o to remove
|
||||
abStart: h.buffer2[0],
|
||||
abLength: h.buffer2[1] // length of a/b to insert
|
||||
// abContent: (ab === 'a' ? a : b).slice(h.buffer2[0], h.buffer2[0] + h.buffer2[1])
|
||||
});
|
||||
}
|
||||
|
||||
diffIndices(o, a).forEach(item => addHunk(item, 'a'));
|
||||
diffIndices(o, b).forEach(item => addHunk(item, 'b'));
|
||||
hunks.sort((x,y) => x.oStart - y.oStart);
|
||||
|
||||
let results = [];
|
||||
let currOffset = 0;
|
||||
|
||||
function advanceTo(endOffset) {
|
||||
if (endOffset > currOffset) {
|
||||
results.push({
|
||||
stable: true,
|
||||
buffer: 'o',
|
||||
bufferStart: currOffset,
|
||||
bufferLength: endOffset - currOffset,
|
||||
bufferContent: o.slice(currOffset, endOffset)
|
||||
});
|
||||
currOffset = endOffset;
|
||||
}
|
||||
}
|
||||
|
||||
while (hunks.length) {
|
||||
let hunk = hunks.shift();
|
||||
let regionStart = hunk.oStart;
|
||||
let regionEnd = hunk.oStart + hunk.oLength;
|
||||
let regionHunks = [hunk];
|
||||
advanceTo(regionStart);
|
||||
|
||||
// Try to pull next overlapping hunk into this region
|
||||
while (hunks.length) {
|
||||
const nextHunk = hunks[0];
|
||||
const nextHunkStart = nextHunk.oStart;
|
||||
if (nextHunkStart > regionEnd) break; // no overlap
|
||||
|
||||
regionEnd = Math.max(regionEnd, nextHunkStart + nextHunk.oLength);
|
||||
regionHunks.push(hunks.shift());
|
||||
}
|
||||
|
||||
if (regionHunks.length === 1) {
|
||||
// Only one hunk touches this region, meaning that there is no conflict here.
|
||||
// Either `a` or `b` is inserting into a region of `o` unchanged by the other.
|
||||
if (hunk.abLength > 0) {
|
||||
const buffer = (hunk.ab === 'a' ? a : b);
|
||||
results.push({
|
||||
stable: true,
|
||||
buffer: hunk.ab,
|
||||
bufferStart: hunk.abStart,
|
||||
bufferLength: hunk.abLength,
|
||||
bufferContent: buffer.slice(hunk.abStart, hunk.abStart + hunk.abLength)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// A true a/b conflict. Determine the bounds involved from `a`, `o`, and `b`.
|
||||
// Effectively merge all the `a` hunks into one giant hunk, then do the
|
||||
// same for the `b` hunks; then, correct for skew in the regions of `o`
|
||||
// that each side changed, and report appropriate spans for the three sides.
|
||||
let bounds = {
|
||||
a: [a.length, -1, o.length, -1],
|
||||
b: [b.length, -1, o.length, -1]
|
||||
};
|
||||
while (regionHunks.length) {
|
||||
hunk = regionHunks.shift();
|
||||
const oStart = hunk.oStart;
|
||||
const oEnd = oStart + hunk.oLength;
|
||||
const abStart = hunk.abStart;
|
||||
const abEnd = abStart + hunk.abLength;
|
||||
let b = bounds[hunk.ab];
|
||||
b[0] = Math.min(abStart, b[0]);
|
||||
b[1] = Math.max(abEnd, b[1]);
|
||||
b[2] = Math.min(oStart, b[2]);
|
||||
b[3] = Math.max(oEnd, b[3]);
|
||||
}
|
||||
|
||||
const aStart = bounds.a[0] + (regionStart - bounds.a[2]);
|
||||
const aEnd = bounds.a[1] + (regionEnd - bounds.a[3]);
|
||||
const bStart = bounds.b[0] + (regionStart - bounds.b[2]);
|
||||
const bEnd = bounds.b[1] + (regionEnd - bounds.b[3]);
|
||||
|
||||
let result = {
|
||||
stable: false,
|
||||
aStart: aStart,
|
||||
aLength: aEnd - aStart,
|
||||
aContent: a.slice(aStart, aEnd),
|
||||
oStart: regionStart,
|
||||
oLength: regionEnd - regionStart,
|
||||
oContent: o.slice(regionStart, regionEnd),
|
||||
bStart: bStart,
|
||||
bLength: bEnd - bStart,
|
||||
bContent: b.slice(bStart, bEnd)
|
||||
};
|
||||
results.push(result);
|
||||
}
|
||||
currOffset = regionEnd;
|
||||
}
|
||||
|
||||
advanceTo(o.length);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
// Applies the output of diff3MergeRegions to actually
|
||||
// construct the merged buffer; the returned result alternates
|
||||
// between 'ok' and 'conflict' blocks.
|
||||
// A "false conflict" is where `a` and `b` both change the same from `o`
|
||||
function diff3Merge(a, o, b, options) {
|
||||
let defaults = {
|
||||
excludeFalseConflicts: true,
|
||||
stringSeparator: /\s+/
|
||||
};
|
||||
options = Object.assign(defaults, options);
|
||||
|
||||
if (typeof a === 'string') a = a.split(options.stringSeparator);
|
||||
if (typeof o === 'string') o = o.split(options.stringSeparator);
|
||||
if (typeof b === 'string') b = b.split(options.stringSeparator);
|
||||
|
||||
let results = [];
|
||||
const regions = diff3MergeRegions(a, o, b);
|
||||
|
||||
let okBuffer = [];
|
||||
function flushOk() {
|
||||
if (okBuffer.length) {
|
||||
results.push({ ok: okBuffer });
|
||||
}
|
||||
okBuffer = [];
|
||||
}
|
||||
|
||||
function isFalseConflict(a, b) {
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
regions.forEach(region => {
|
||||
if (region.stable) {
|
||||
okBuffer.push(...region.bufferContent);
|
||||
} else {
|
||||
if (options.excludeFalseConflicts && isFalseConflict(region.aContent, region.bContent)) {
|
||||
okBuffer.push(...region.aContent);
|
||||
} else {
|
||||
flushOk();
|
||||
results.push({
|
||||
conflict: {
|
||||
a: region.aContent,
|
||||
aIndex: region.aStart,
|
||||
o: region.oContent,
|
||||
oIndex: region.oStart,
|
||||
b: region.bContent,
|
||||
bIndex: region.bStart
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
flushOk();
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
function mergeDiff3(a, o, b, options) {
|
||||
const defaults = {
|
||||
excludeFalseConflicts: true,
|
||||
stringSeparator: /\s+/,
|
||||
label: {}
|
||||
};
|
||||
options = Object.assign(defaults, options);
|
||||
|
||||
const aSection = '<<<<<<<' + (options.label.a ? ` ${options.label.a}` : '');
|
||||
const oSection = '|||||||' + (options.label.o ? ` ${options.label.o}` : '');
|
||||
const xSection = '=======';
|
||||
const bSection = '>>>>>>>' + (options.label.b ? ` ${options.label.b}` : '');
|
||||
|
||||
const regions = diff3Merge(a, o, b, options);
|
||||
let conflict = false;
|
||||
let result = [];
|
||||
|
||||
regions.forEach(region => {
|
||||
if (region.ok) {
|
||||
result = result.concat(region.ok);
|
||||
} else if (region.conflict) {
|
||||
conflict = true;
|
||||
result = result.concat(
|
||||
[aSection],
|
||||
region.conflict.a,
|
||||
[oSection],
|
||||
region.conflict.o,
|
||||
[xSection],
|
||||
region.conflict.b,
|
||||
[bSection]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
conflict: conflict,
|
||||
result: result
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function merge(a, o, b, options) {
|
||||
const defaults = {
|
||||
excludeFalseConflicts: true,
|
||||
stringSeparator: /\s+/,
|
||||
label: {}
|
||||
};
|
||||
options = Object.assign(defaults, options);
|
||||
|
||||
const aSection = '<<<<<<<' + (options.label.a ? ` ${options.label.a}` : '');
|
||||
const xSection = '=======';
|
||||
const bSection = '>>>>>>>' + (options.label.b ? ` ${options.label.b}` : '');
|
||||
|
||||
const regions = diff3Merge(a, o, b, options);
|
||||
let conflict = false;
|
||||
let result = [];
|
||||
|
||||
regions.forEach(region => {
|
||||
if (region.ok) {
|
||||
result = result.concat(region.ok);
|
||||
} else if (region.conflict) {
|
||||
conflict = true;
|
||||
result = result.concat(
|
||||
[aSection],
|
||||
region.conflict.a,
|
||||
[xSection],
|
||||
region.conflict.b,
|
||||
[bSection]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
conflict: conflict,
|
||||
result: result
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function mergeDigIn(a, o, b, options) {
|
||||
const defaults = {
|
||||
excludeFalseConflicts: true,
|
||||
stringSeparator: /\s+/,
|
||||
label: {}
|
||||
};
|
||||
options = Object.assign(defaults, options);
|
||||
|
||||
const aSection = '<<<<<<<' + (options.label.a ? ` ${options.label.a}` : '');
|
||||
const xSection = '=======';
|
||||
const bSection = '>>>>>>>' + (options.label.b ? ` ${options.label.b}` : '');
|
||||
|
||||
const regions = diff3Merge(a, o, b, options);
|
||||
let conflict = false;
|
||||
let result = [];
|
||||
|
||||
regions.forEach(region => {
|
||||
if (region.ok) {
|
||||
result = result.concat(region.ok);
|
||||
} else {
|
||||
const c = diffComm(region.conflict.a, region.conflict.b);
|
||||
for (let j = 0; j < c.length; j++) {
|
||||
let inner = c[j];
|
||||
if (inner.common) {
|
||||
result = result.concat(inner.common);
|
||||
} else {
|
||||
conflict = true;
|
||||
result = result.concat(
|
||||
[aSection],
|
||||
inner.buffer1,
|
||||
[xSection],
|
||||
inner.buffer2,
|
||||
[bSection]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
conflict: conflict,
|
||||
result: result
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Applies a patch to a buffer.
|
||||
// Given buffer1 and buffer2, `patch(buffer1, diffPatch(buffer1, buffer2))` should give buffer2.
|
||||
function patch(buffer, patch) {
|
||||
let result = [];
|
||||
let currOffset = 0;
|
||||
|
||||
function advanceTo(targetOffset) {
|
||||
while (currOffset < targetOffset) {
|
||||
result.push(buffer[currOffset]);
|
||||
currOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
for (let chunkIndex = 0; chunkIndex < patch.length; chunkIndex++) {
|
||||
let chunk = patch[chunkIndex];
|
||||
advanceTo(chunk.buffer1.offset);
|
||||
for (let itemIndex = 0; itemIndex < chunk.buffer2.chunk.length; itemIndex++) {
|
||||
result.push(chunk.buffer2.chunk[itemIndex]);
|
||||
}
|
||||
currOffset += chunk.buffer1.length;
|
||||
}
|
||||
|
||||
advanceTo(buffer.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Takes the output of diffPatch(), and removes extra information from it.
|
||||
// It can still be used by patch(), below, but can no longer be inverted.
|
||||
function stripPatch(patch) {
|
||||
return patch.map(chunk => ({
|
||||
buffer1: { offset: chunk.buffer1.offset, length: chunk.buffer1.length },
|
||||
buffer2: { chunk: chunk.buffer2.chunk }
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
// Takes the output of diffPatch(), and inverts the sense of it, so that it
|
||||
// can be applied to buffer2 to give buffer1 rather than the other way around.
|
||||
function invertPatch(patch) {
|
||||
return patch.map(chunk => ({
|
||||
buffer1: chunk.buffer2,
|
||||
buffer2: chunk.buffer1
|
||||
}));
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ export abstract class Disposable {
|
|||
|
||||
protected _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public dispose(): any {
|
||||
public dispose(): void {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,10 +51,12 @@ export function createInterceptingOutputChannel(
|
|||
private input: Readable;
|
||||
|
||||
append(value: string): void {
|
||||
this.logger.debug("LSP Message: " + value);
|
||||
this.input.push(value);
|
||||
}
|
||||
|
||||
appendLine(value: string): void {
|
||||
this.logger.debug("LSP Message: " + value);
|
||||
this.input.push(value);
|
||||
}
|
||||
|
||||
|
|
|
|||
9
js/VSC-Extension/src/utils/rootWorkspace.ts
Normal file
9
js/VSC-Extension/src/utils/rootWorkspace.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import * as vscode from "vscode";
|
||||
|
||||
export const getRootWorkspaceFolder = (): vscode.WorkspaceFolder | null => {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (!workspaceFolder) {
|
||||
return null;
|
||||
}
|
||||
return workspaceFolder;
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,226 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="
|
||||
default-src 'none';
|
||||
style-src ${webview.cspSource} 'unsafe-inline';
|
||||
font-src ${webview.cspSource};
|
||||
script-src ${webview.cspSource} 'nonce-${nonce}';
|
||||
img-src ${webview.cspSource} https:;
|
||||
"
|
||||
/>
|
||||
<!-- style -->
|
||||
<link href="${styleUri}" rel="stylesheet" />
|
||||
<!-- codicons icons -->
|
||||
<link href="${codiconsUri}" rel="stylesheet" />
|
||||
<title>Codeflash Analysis</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="app-container" style="display: none">
|
||||
<!-- File context is tracked internally but not displayed -->
|
||||
<span id="analyzed-file" class="hidden-element">None</span>
|
||||
|
||||
<div class="api-key-container" style="display: none">
|
||||
<h2>Codeflash API Key</h2>
|
||||
<div class="input-group">
|
||||
<input type="password" id="api-key" placeholder="cf-******" />
|
||||
</div>
|
||||
<p class="hint">
|
||||
You can generate an API key from your
|
||||
<a href="https://app.codeflash.ai/apikeys" target="_blank"
|
||||
>Codeflash account</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="optimize-current-diff"
|
||||
class="optimize-button center"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
<span class="codicon codicon-github-action"></span>
|
||||
<span>Optimize modified code</span>
|
||||
</button>
|
||||
|
||||
<div class="content-card" style="display: none">
|
||||
<div class="section-header">
|
||||
<div class="section-title">
|
||||
<span class="codicon codicon-lightbulb"></span>
|
||||
<span>Optimizable Functions</span>
|
||||
<span
|
||||
id="function-count"
|
||||
class="badge"
|
||||
title="Number of optimizable functions found"
|
||||
></span>
|
||||
<button
|
||||
id="refresh-button-secondary"
|
||||
title="Refresh Analysis"
|
||||
class="action-button refresh-inline"
|
||||
>
|
||||
<span class="codicon codicon-refresh"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="functions-container" class="functions-wrapper">
|
||||
<ul id="function-list" class="function-list"></ul>
|
||||
<div id="placeholder-message" class="placeholder-card">
|
||||
<span class="codicon codicon-search"></span>
|
||||
<p>Waiting for analysis...</p>
|
||||
<button
|
||||
id="create-sample-button"
|
||||
class="action-button sample-button"
|
||||
style="display: none"
|
||||
>
|
||||
<span class="codicon codicon-file-add"></span>
|
||||
Create Sample File
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Optimization Progress Banner -->
|
||||
<div
|
||||
id="optimization-banner"
|
||||
class="optimization-banner"
|
||||
style="display: none"
|
||||
>
|
||||
<div class="optimization-header">
|
||||
<div class="optimization-title-section">
|
||||
<span class="codicon codicon-rocket optimization-icon"></span>
|
||||
<div class="optimization-title-text">
|
||||
<span class="optimization-action">Optimizing</span>
|
||||
<span class="optimization-function" id="current-function-name"
|
||||
>function_name</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
id="cancel-optimization"
|
||||
class="cancel-btn"
|
||||
title="Cancel optimization"
|
||||
>
|
||||
<span class="codicon codicon-close"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- TODO: completely remove the progress steps -->
|
||||
<div class="optimization-progress-section">
|
||||
<div class="optimization-steps-container">
|
||||
<div class="optimization-step-item" id="initializing-step">
|
||||
<div class="step-indicator">
|
||||
<span class="codicon codicon-beaker"></span>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="step-label">Initializing</div>
|
||||
<div class="step-result" id="step-0-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="optimization-step-item" id="step-discover-tests">
|
||||
<div class="step-indicator">
|
||||
<span class="codicon codicon-beaker"></span>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="step-label">Discovering unit tests</div>
|
||||
<div class="step-result" id="step-1-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="optimization-step-item"
|
||||
id="tests-optimization-generation"
|
||||
>
|
||||
<div class="step-indicator">
|
||||
<span class="codicon codicon-search"></span>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="step-label">Generating tests and optimizations</div>
|
||||
<div class="step-result" id="step-2-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="optimization-step-item" id="step-establish-baseline">
|
||||
<div class="step-indicator">
|
||||
<span class="codicon codicon-edit"></span>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="step-label">Establishing code baseline</div>
|
||||
<div class="step-result" id="step-3-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="optimization-step-item"
|
||||
id="step-optimization-candidate-tests"
|
||||
>
|
||||
<div class="step-indicator">
|
||||
<span class="codicon codicon-check"></span>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="step-label">Testing optimization candidates</div>
|
||||
<div class="step-result" id="step-4-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="optimization-step-item" id="step-finalize">
|
||||
<div class="step-indicator">
|
||||
<span class="codicon codicon-check-all"></span>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<div class="step-label">Finalizing optimization</div>
|
||||
<div class="step-result" id="step-5-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="optimization-results"
|
||||
class="optimization-results"
|
||||
style="display: none"
|
||||
>
|
||||
<button
|
||||
id="view-function-list-btn"
|
||||
class="action-button view-functions-btn"
|
||||
>
|
||||
<span class="codicon codicon-list-selection"></span>
|
||||
Return to Function List
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="queue-tasks" class="queue-tasks">
|
||||
<div class="queue-tasks-header" id="queue-tasks-toggle">
|
||||
<div class="queue-tasks-title">
|
||||
<span
|
||||
class="codicon codicon-chevron-down queue-tasks-chevron"
|
||||
></span>
|
||||
<span class="codicon codicon-list-flat"></span>
|
||||
<span>⚡ Optimizations</span>
|
||||
<span
|
||||
id="queue-task-count"
|
||||
class="badge"
|
||||
style="display: none"
|
||||
></span>
|
||||
</div>
|
||||
<button
|
||||
id="logs-button"
|
||||
title="Open Output Panel"
|
||||
class="action-button"
|
||||
>
|
||||
<span class="codicon codicon-output"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="queue-tasks-content expanded" id="queue-tasks-content">
|
||||
<ul id="queue-tasks-list" class="queue-tasks-list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"module": "es2022", // or keep nodenext if needed
|
||||
"moduleResolution": "bundler", // 👈 classic behavior
|
||||
"target": "ES2022",
|
||||
|
|
|
|||
|
|
@ -11,17 +11,23 @@ export async function getOptimizationEventById({
|
|||
userId: string
|
||||
trace_id: string
|
||||
}) {
|
||||
const event = await prisma.optimization_events.findUnique({
|
||||
// TODO: Cache repoIds
|
||||
const repoMemberships = await prisma.repository_members.findMany({
|
||||
where: { user_id: userId },
|
||||
select: { repository_id: true },
|
||||
})
|
||||
const repoIds = repoMemberships.map(r => r.repository_id)
|
||||
|
||||
const event = await prisma.optimization_events.findFirst({
|
||||
where: {
|
||||
trace_id,
|
||||
user_id: userId,
|
||||
is_staging: true,
|
||||
OR: [{ user_id: userId }, { repository_id: { in: repoIds } }],
|
||||
},
|
||||
include: {
|
||||
repository: true,
|
||||
},
|
||||
})
|
||||
|
||||
return event
|
||||
}
|
||||
export async function saveOptimizationChanges({
|
||||
|
|
|
|||
|
|
@ -16,13 +16,26 @@ export async function getAllOptimizationEvents({
|
|||
page?: number
|
||||
pageSize?: number
|
||||
}) {
|
||||
const repoMembers = await prisma.repository_members.findMany({
|
||||
where: { user_id: userId },
|
||||
select: { repository_id: true },
|
||||
})
|
||||
const repoIds = repoMembers.map(rm => rm.repository_id)
|
||||
|
||||
const where: any = {
|
||||
user_id: userId,
|
||||
is_staging: true,
|
||||
OR: [
|
||||
{
|
||||
user_id: userId,
|
||||
},
|
||||
{
|
||||
repository_id: { in: repoIds },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
where.OR.push(
|
||||
{
|
||||
function_name: {
|
||||
contains: search,
|
||||
|
|
@ -35,7 +48,7 @@ export async function getAllOptimizationEvents({
|
|||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue