mito-ai 0.1.45__py3-none-any.whl → 0.1.46__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mito_ai/__init__.py +10 -1
- mito_ai/_version.py +1 -1
- mito_ai/anthropic_client.py +90 -5
- mito_ai/chat_history/handlers.py +63 -0
- mito_ai/chat_history/urls.py +32 -0
- mito_ai/completions/handlers.py +18 -20
- mito_ai/constants.py +3 -0
- mito_ai/streamlit_conversion/agent_utils.py +148 -30
- mito_ai/streamlit_conversion/prompts/prompt_constants.py +147 -24
- mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +2 -1
- mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +2 -2
- mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +4 -3
- mito_ai/streamlit_conversion/prompts/update_existing_app_prompt.py +50 -0
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +101 -107
- mito_ai/streamlit_conversion/streamlit_system_prompt.py +1 -0
- mito_ai/streamlit_conversion/streamlit_utils.py +13 -10
- mito_ai/streamlit_conversion/validate_streamlit_app.py +77 -82
- mito_ai/streamlit_preview/handlers.py +3 -4
- mito_ai/streamlit_preview/utils.py +11 -7
- mito_ai/tests/chat_history/test_chat_history.py +211 -0
- mito_ai/tests/message_history/test_message_history_utils.py +43 -19
- mito_ai/tests/providers/test_anthropic_client.py +178 -6
- mito_ai/tests/streamlit_conversion/test_apply_patch_to_text.py +368 -0
- mito_ai/tests/streamlit_conversion/test_fix_diff_headers.py +533 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +71 -74
- mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +16 -16
- mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +17 -14
- mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +2 -2
- mito_ai/tests/user/__init__.py +2 -0
- mito_ai/tests/user/test_user.py +120 -0
- mito_ai/user/handlers.py +33 -0
- mito_ai/user/urls.py +21 -0
- mito_ai/utils/anthropic_utils.py +8 -6
- mito_ai/utils/message_history_utils.py +4 -3
- mito_ai/utils/telemetry_utils.py +7 -4
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +1 -1
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
- mito_ai-0.1.45.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.0c3368195d954d2ed033.js → mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.20f12766ecd3d430568e.js +955 -173
- mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.20f12766ecd3d430568e.js.map +1 -0
- mito_ai-0.1.45.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.684f82575fcc2e3b350c.js → mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.54126ab6511271265443.js +5 -5
- mito_ai-0.1.45.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.684f82575fcc2e3b350c.js.map → mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.54126ab6511271265443.js.map +1 -1
- {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/METADATA +1 -1
- {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/RECORD +68 -58
- mito_ai-0.1.45.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.0c3368195d954d2ed033.js.map +0 -1
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js.map +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js.map +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js.map +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
- {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
-
from mito_ai.anthropic_client import get_anthropic_system_prompt_and_messages, extract_and_parse_anthropic_json_response, AnthropicClient
|
|
6
|
-
from mito_ai.utils.anthropic_utils import
|
|
5
|
+
from mito_ai.anthropic_client import get_anthropic_system_prompt_and_messages, get_anthropic_system_prompt_and_messages_with_caching, add_cache_control_to_message, extract_and_parse_anthropic_json_response, AnthropicClient
|
|
6
|
+
from mito_ai.utils.anthropic_utils import FAST_ANTHROPIC_MODEL
|
|
7
7
|
from anthropic.types import Message, TextBlock, ToolUseBlock, Usage, ToolUseBlock, Message, Usage, TextBlock
|
|
8
8
|
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionUserMessageParam, ChatCompletionAssistantMessageParam, ChatCompletionSystemMessageParam
|
|
9
|
-
from mito_ai.completions.models import MessageType
|
|
10
|
-
from unittest.mock import
|
|
9
|
+
from mito_ai.completions.models import MessageType
|
|
10
|
+
from unittest.mock import patch
|
|
11
11
|
import anthropic
|
|
12
|
-
from typing import List, Dict,
|
|
12
|
+
from typing import List, Dict, cast
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# Dummy base64 image (1x1 PNG)
|
|
@@ -272,4 +272,176 @@ async def test_model_selection_based_on_message_type(message_type, expected_mode
|
|
|
272
272
|
# Verify that create was called with the expected model
|
|
273
273
|
mock_create.assert_called_once()
|
|
274
274
|
call_args = mock_create.call_args
|
|
275
|
-
assert call_args[1]['model'] == expected_model
|
|
275
|
+
assert call_args[1]['model'] == expected_model
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Caching Tests
|
|
279
|
+
|
|
280
|
+
@pytest.mark.parametrize("message,expected_role,expected_content_type,expected_content_length,expected_cache_control", [
|
|
281
|
+
# String content message
|
|
282
|
+
(
|
|
283
|
+
{"role": "user", "content": "Hello world"},
|
|
284
|
+
"user",
|
|
285
|
+
list,
|
|
286
|
+
1,
|
|
287
|
+
True
|
|
288
|
+
),
|
|
289
|
+
# List content message
|
|
290
|
+
(
|
|
291
|
+
{
|
|
292
|
+
"role": "user",
|
|
293
|
+
"content": [
|
|
294
|
+
{"type": "text", "text": "First part"},
|
|
295
|
+
{"type": "text", "text": "Second part"}
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
"user",
|
|
299
|
+
list,
|
|
300
|
+
2,
|
|
301
|
+
True
|
|
302
|
+
),
|
|
303
|
+
# Empty content message
|
|
304
|
+
(
|
|
305
|
+
{"role": "user", "content": []},
|
|
306
|
+
"user",
|
|
307
|
+
list,
|
|
308
|
+
0,
|
|
309
|
+
False
|
|
310
|
+
),
|
|
311
|
+
# Assistant message with string content
|
|
312
|
+
(
|
|
313
|
+
{"role": "assistant", "content": "I can help you with that."},
|
|
314
|
+
"assistant",
|
|
315
|
+
list,
|
|
316
|
+
1,
|
|
317
|
+
True
|
|
318
|
+
),
|
|
319
|
+
])
|
|
320
|
+
def test_add_cache_control_to_message(message, expected_role, expected_content_type, expected_content_length, expected_cache_control):
|
|
321
|
+
"""Test adding cache control to different types of messages."""
|
|
322
|
+
result = add_cache_control_to_message(message)
|
|
323
|
+
|
|
324
|
+
assert result["role"] == expected_role
|
|
325
|
+
assert isinstance(result["content"], expected_content_type)
|
|
326
|
+
assert len(result["content"]) == expected_content_length
|
|
327
|
+
|
|
328
|
+
if expected_cache_control and expected_content_length > 0:
|
|
329
|
+
# Should have cache_control on the last content block
|
|
330
|
+
last_block = result["content"][-1]
|
|
331
|
+
assert last_block["cache_control"] == {"type": "ephemeral"}
|
|
332
|
+
|
|
333
|
+
# If there are multiple blocks, earlier blocks should not have cache_control
|
|
334
|
+
if expected_content_length > 1:
|
|
335
|
+
for i in range(expected_content_length - 1):
|
|
336
|
+
assert "cache_control" not in result["content"][i]
|
|
337
|
+
elif expected_content_length == 0:
|
|
338
|
+
# Empty content should return unchanged
|
|
339
|
+
assert result == message
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@pytest.mark.parametrize("messages,expected_system_type,expected_system_content", [
|
|
343
|
+
# With system prompt
|
|
344
|
+
(
|
|
345
|
+
[
|
|
346
|
+
ChatCompletionSystemMessageParam(role="system", content="You are a helpful assistant."),
|
|
347
|
+
ChatCompletionUserMessageParam(role="user", content="Hello!")
|
|
348
|
+
],
|
|
349
|
+
list,
|
|
350
|
+
"You are a helpful assistant.",
|
|
351
|
+
),
|
|
352
|
+
# Without system prompt
|
|
353
|
+
(
|
|
354
|
+
[
|
|
355
|
+
ChatCompletionUserMessageParam(role="user", content="Hello!"),
|
|
356
|
+
ChatCompletionAssistantMessageParam(role="assistant", content="Hi there!")
|
|
357
|
+
],
|
|
358
|
+
anthropic.Omit,
|
|
359
|
+
None,
|
|
360
|
+
),
|
|
361
|
+
# Multiple system messages (should take last one)
|
|
362
|
+
(
|
|
363
|
+
[
|
|
364
|
+
ChatCompletionSystemMessageParam(role="system", content="First system message."),
|
|
365
|
+
ChatCompletionSystemMessageParam(role="system", content="Second system message."),
|
|
366
|
+
ChatCompletionUserMessageParam(role="user", content="Hello!"),
|
|
367
|
+
ChatCompletionUserMessageParam(role="user", content="Hello!"),
|
|
368
|
+
ChatCompletionUserMessageParam(role="user", content="Hello!")
|
|
369
|
+
],
|
|
370
|
+
list,
|
|
371
|
+
"Second system message.",
|
|
372
|
+
),
|
|
373
|
+
])
|
|
374
|
+
def test_caching_system_prompt_scenarios(messages, expected_system_type, expected_system_content):
|
|
375
|
+
"""Test caching with different system prompt scenarios."""
|
|
376
|
+
system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages_with_caching(messages)
|
|
377
|
+
|
|
378
|
+
# Check system prompt
|
|
379
|
+
assert isinstance(system_prompt, expected_system_type)
|
|
380
|
+
if expected_system_content:
|
|
381
|
+
assert system_prompt[0]["text"] == expected_system_content
|
|
382
|
+
assert system_prompt[0]["cache_control"] == {"type": "ephemeral"}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@pytest.mark.parametrize("message_count,expected_cache_boundary", [
|
|
386
|
+
(1, None), # 1 message, No cache boundary
|
|
387
|
+
(3, None), # 3 messages, No cache boundary
|
|
388
|
+
(5, 1), # 5 messages, cache at index 2
|
|
389
|
+
(10, 6), # 10 messages, cache at index 6
|
|
390
|
+
])
|
|
391
|
+
def test_caching_conversation_history(message_count, expected_cache_boundary):
|
|
392
|
+
"""Test that conversation history is cached at the keep_recent boundary for different message counts."""
|
|
393
|
+
|
|
394
|
+
# Create messages based on the parameter
|
|
395
|
+
messages: List[ChatCompletionMessageParam] = [
|
|
396
|
+
ChatCompletionSystemMessageParam(role="system", content="You are helpful.")
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
# Add message pairs
|
|
400
|
+
for i in range(message_count):
|
|
401
|
+
messages.append(ChatCompletionUserMessageParam(role="user", content=f"Message {i+1}"))
|
|
402
|
+
|
|
403
|
+
system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages_with_caching(messages)
|
|
404
|
+
|
|
405
|
+
# System prompt should have cache control
|
|
406
|
+
assert isinstance(system_prompt, list)
|
|
407
|
+
assert system_prompt[0]["cache_control"] == {"type": "ephemeral"}
|
|
408
|
+
|
|
409
|
+
print(anthropic_messages)
|
|
410
|
+
|
|
411
|
+
if expected_cache_boundary is None:
|
|
412
|
+
# Verify no cache boundry
|
|
413
|
+
assert all("cache_control" not in str(message) for message in anthropic_messages)
|
|
414
|
+
else:
|
|
415
|
+
# Other messages should not have cache control
|
|
416
|
+
for i, message in enumerate(anthropic_messages):
|
|
417
|
+
if i == expected_cache_boundary:
|
|
418
|
+
assert anthropic_messages[expected_cache_boundary]["content"][0]["cache_control"] == {"type": "ephemeral"}
|
|
419
|
+
else:
|
|
420
|
+
assert "cache_control" not in str(message)
|
|
421
|
+
|
|
422
|
+
def test_caching_with_mixed_content():
|
|
423
|
+
"""Test caching with mixed text and image content."""
|
|
424
|
+
messages: List[ChatCompletionMessageParam] = [
|
|
425
|
+
ChatCompletionSystemMessageParam(role="system", content="You are a helpful assistant."),
|
|
426
|
+
ChatCompletionUserMessageParam(role="user", content=[
|
|
427
|
+
{"type": "text", "text": "Here is an image:"},
|
|
428
|
+
{"type": "image_url", "image_url": {"url": DUMMY_IMAGE_DATA_URL}}
|
|
429
|
+
])
|
|
430
|
+
]
|
|
431
|
+
system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages_with_caching(messages)
|
|
432
|
+
|
|
433
|
+
# System prompt should have cache control
|
|
434
|
+
assert isinstance(system_prompt, list)
|
|
435
|
+
assert system_prompt[0]["cache_control"] == {"type": "ephemeral"}
|
|
436
|
+
|
|
437
|
+
# User message should NOT have cache control (only 1 message, so boundary is invalid)
|
|
438
|
+
user_message = anthropic_messages[0]
|
|
439
|
+
assert user_message["role"] == "user"
|
|
440
|
+
assert isinstance(user_message["content"], list)
|
|
441
|
+
assert len(user_message["content"]) == 2
|
|
442
|
+
|
|
443
|
+
# No content blocks should have cache control (too few messages to cache)
|
|
444
|
+
assert user_message["content"][0]["type"] == "text"
|
|
445
|
+
assert "cache_control" not in user_message["content"][0]
|
|
446
|
+
assert user_message["content"][1]["type"] == "image"
|
|
447
|
+
assert "cache_control" not in user_message["content"][1]
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from mito_ai.streamlit_conversion.agent_utils import apply_patch_to_text
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.parametrize("original_text,diff,expected_result", [
|
|
9
|
+
# Test case 1: Simple line replacement
|
|
10
|
+
(
|
|
11
|
+
"""import streamlit as st
|
|
12
|
+
|
|
13
|
+
st.markdown(\"\"\"
|
|
14
|
+
<style>
|
|
15
|
+
#MainMenu {visibility: hidden;}
|
|
16
|
+
.stAppDeployButton {display:none;}
|
|
17
|
+
footer {visibility: hidden;}
|
|
18
|
+
.stMainBlockContainer {padding: 2rem 1rem 2rem 1rem;}
|
|
19
|
+
</style>
|
|
20
|
+
\"\"\", unsafe_allow_html=True)
|
|
21
|
+
|
|
22
|
+
st.title("Simple Calculation")
|
|
23
|
+
|
|
24
|
+
x = 5
|
|
25
|
+
y = 10
|
|
26
|
+
result = x + y
|
|
27
|
+
|
|
28
|
+
st.write(f"x = {x}")
|
|
29
|
+
st.write(f"y = {y}")
|
|
30
|
+
st.write(f"x + y = {result}")""",
|
|
31
|
+
"""--- a/app.py
|
|
32
|
+
+++ b/app.py
|
|
33
|
+
@@ -10,4 +10,4 @@
|
|
34
|
+
\"\"\", unsafe_allow_html=True)
|
|
35
|
+
|
|
36
|
+
-st.title("Simple Calculation")
|
|
37
|
+
+st.title("Math Examples")
|
|
38
|
+
|
|
39
|
+
x = 5""",
|
|
40
|
+
"""import streamlit as st
|
|
41
|
+
|
|
42
|
+
st.markdown(\"\"\"
|
|
43
|
+
<style>
|
|
44
|
+
#MainMenu {visibility: hidden;}
|
|
45
|
+
.stAppDeployButton {display:none;}
|
|
46
|
+
footer {visibility: hidden;}
|
|
47
|
+
.stMainBlockContainer {padding: 2rem 1rem 2rem 1rem;}
|
|
48
|
+
</style>
|
|
49
|
+
\"\"\", unsafe_allow_html=True)
|
|
50
|
+
|
|
51
|
+
st.title("Math Examples")
|
|
52
|
+
|
|
53
|
+
x = 5
|
|
54
|
+
y = 10
|
|
55
|
+
result = x + y
|
|
56
|
+
|
|
57
|
+
st.write(f"x = {x}")
|
|
58
|
+
st.write(f"y = {y}")
|
|
59
|
+
st.write(f"x + y = {result}")"""
|
|
60
|
+
),
|
|
61
|
+
|
|
62
|
+
# Test case 2: Add new lines
|
|
63
|
+
(
|
|
64
|
+
"""import streamlit as st
|
|
65
|
+
|
|
66
|
+
st.title("My App")""",
|
|
67
|
+
"""--- a/app.py
|
|
68
|
+
+++ b/app.py
|
|
69
|
+
@@ -1,3 +1,5 @@
|
|
70
|
+
import streamlit as st
|
|
71
|
+
|
|
72
|
+
+st.header("Welcome")
|
|
73
|
+
st.title("My App")
|
|
74
|
+
+st.write("This is a test app")""",
|
|
75
|
+
"""import streamlit as st
|
|
76
|
+
|
|
77
|
+
st.header("Welcome")
|
|
78
|
+
st.title("My App")
|
|
79
|
+
st.write("This is a test app")"""
|
|
80
|
+
),
|
|
81
|
+
|
|
82
|
+
# Test case 3: Remove lines
|
|
83
|
+
(
|
|
84
|
+
"""import streamlit as st
|
|
85
|
+
|
|
86
|
+
st.header("Welcome")
|
|
87
|
+
st.title("My App")
|
|
88
|
+
st.write("This is a test app")""",
|
|
89
|
+
"""--- a/app.py
|
|
90
|
+
+++ b/app.py
|
|
91
|
+
@@ -1,5 +1,3 @@
|
|
92
|
+
import streamlit as st
|
|
93
|
+
|
|
94
|
+
-st.header("Welcome")
|
|
95
|
+
st.title("My App")
|
|
96
|
+
-st.write("This is a test app")""",
|
|
97
|
+
"""import streamlit as st
|
|
98
|
+
|
|
99
|
+
st.title("My App")
|
|
100
|
+
"""
|
|
101
|
+
),
|
|
102
|
+
|
|
103
|
+
# Test case 4: Single hunk with multiple changes
|
|
104
|
+
(
|
|
105
|
+
"""import streamlit as st
|
|
106
|
+
|
|
107
|
+
st.title("Old Title")
|
|
108
|
+
x = 5
|
|
109
|
+
y = 10
|
|
110
|
+
st.write("Old message")""",
|
|
111
|
+
"""--- a/app.py
|
|
112
|
+
+++ b/app.py
|
|
113
|
+
@@ -1,6 +1,6 @@
|
|
114
|
+
import streamlit as st
|
|
115
|
+
|
|
116
|
+
-st.title("Old Title")
|
|
117
|
+
+st.title("New Title")
|
|
118
|
+
x = 5
|
|
119
|
+
y = 10
|
|
120
|
+
-st.write("Old message")
|
|
121
|
+
+st.write("New message")""",
|
|
122
|
+
"""import streamlit as st
|
|
123
|
+
|
|
124
|
+
st.title("New Title")
|
|
125
|
+
x = 5
|
|
126
|
+
y = 10
|
|
127
|
+
st.write("New message")"""
|
|
128
|
+
),
|
|
129
|
+
|
|
130
|
+
# Test case 5: Empty diff
|
|
131
|
+
(
|
|
132
|
+
"""import streamlit as st
|
|
133
|
+
|
|
134
|
+
st.title("My App")""",
|
|
135
|
+
"",
|
|
136
|
+
"""import streamlit as st
|
|
137
|
+
|
|
138
|
+
st.title("My App")"""
|
|
139
|
+
),
|
|
140
|
+
|
|
141
|
+
# Test case 6: Whitespace-only changes
|
|
142
|
+
(
|
|
143
|
+
"""import streamlit as st
|
|
144
|
+
st.title("My App")""",
|
|
145
|
+
"""--- a/app.py
|
|
146
|
+
+++ b/app.py
|
|
147
|
+
@@ -1,2 +1,2 @@
|
|
148
|
+
import streamlit as st
|
|
149
|
+
-st.title("My App")
|
|
150
|
+
+st.title("My App")""",
|
|
151
|
+
"""import streamlit as st
|
|
152
|
+
st.title("My App")"""
|
|
153
|
+
),
|
|
154
|
+
|
|
155
|
+
# Test case 7: Replace multiple consecutive lines
|
|
156
|
+
(
|
|
157
|
+
"""import streamlit as st
|
|
158
|
+
|
|
159
|
+
st.title("My App")
|
|
160
|
+
st.write("Line 1")
|
|
161
|
+
st.write("Line 2")
|
|
162
|
+
st.write("Line 3")
|
|
163
|
+
|
|
164
|
+
x = 5""",
|
|
165
|
+
"""--- a/app.py
|
|
166
|
+
+++ b/app.py
|
|
167
|
+
@@ -3,4 +3,2 @@
|
|
168
|
+
st.title("My App")
|
|
169
|
+
-st.write("Line 1")
|
|
170
|
+
-st.write("Line 2")
|
|
171
|
+
-st.write("Line 3")
|
|
172
|
+
+st.write("New content")
|
|
173
|
+
|
|
174
|
+
x = 5""",
|
|
175
|
+
"""import streamlit as st
|
|
176
|
+
|
|
177
|
+
st.title("My App")
|
|
178
|
+
st.write("New content")
|
|
179
|
+
|
|
180
|
+
x = 5"""
|
|
181
|
+
),
|
|
182
|
+
|
|
183
|
+
# Test case 8: Add lines at the beginning
|
|
184
|
+
(
|
|
185
|
+
"""import streamlit as st
|
|
186
|
+
|
|
187
|
+
st.title("My App")""",
|
|
188
|
+
"""--- a/app.py
|
|
189
|
+
+++ b/app.py
|
|
190
|
+
@@ -1,2 +1,3 @@
|
|
191
|
+
+import pandas as pd
|
|
192
|
+
import streamlit as st
|
|
193
|
+
|
|
194
|
+
st.title("My App")""",
|
|
195
|
+
"""import pandas as pd
|
|
196
|
+
import streamlit as st
|
|
197
|
+
|
|
198
|
+
st.title("My App")"""
|
|
199
|
+
),
|
|
200
|
+
|
|
201
|
+
# Test case 9: Add lines at the end
|
|
202
|
+
(
|
|
203
|
+
"""import streamlit as st
|
|
204
|
+
|
|
205
|
+
st.title("My App")""",
|
|
206
|
+
"""--- a/app.py
|
|
207
|
+
+++ b/app.py
|
|
208
|
+
@@ -3,1 +3,4 @@
|
|
209
|
+
st.title("My App")
|
|
210
|
+
+
|
|
211
|
+
+st.write("Footer content")
|
|
212
|
+
+st.write("More footer")""",
|
|
213
|
+
"""import streamlit as st
|
|
214
|
+
|
|
215
|
+
st.title("My App")
|
|
216
|
+
|
|
217
|
+
st.write("Footer content")
|
|
218
|
+
st.write("More footer")"""
|
|
219
|
+
),
|
|
220
|
+
|
|
221
|
+
# Test case 10: Complex replacement with context
|
|
222
|
+
(
|
|
223
|
+
"""import streamlit as st
|
|
224
|
+
|
|
225
|
+
# This is a comment
|
|
226
|
+
st.title("Old Title")
|
|
227
|
+
# Another comment
|
|
228
|
+
x = 5
|
|
229
|
+
y = 10
|
|
230
|
+
# Final comment""",
|
|
231
|
+
"""--- a/app.py
|
|
232
|
+
+++ b/app.py
|
|
233
|
+
@@ -2,4 +2,4 @@
|
|
234
|
+
|
|
235
|
+
# This is a comment
|
|
236
|
+
-st.title("Old Title")
|
|
237
|
+
+st.title("New Title")
|
|
238
|
+
# Another comment
|
|
239
|
+
x = 5""",
|
|
240
|
+
"""import streamlit as st
|
|
241
|
+
|
|
242
|
+
# This is a comment
|
|
243
|
+
st.title("New Title")
|
|
244
|
+
# Another comment
|
|
245
|
+
x = 5
|
|
246
|
+
y = 10
|
|
247
|
+
# Final comment"""
|
|
248
|
+
),
|
|
249
|
+
|
|
250
|
+
# Test case 11: Simple multiple hunks - title change and add footer
|
|
251
|
+
(
|
|
252
|
+
"""import streamlit as st
|
|
253
|
+
|
|
254
|
+
st.title("Old App")
|
|
255
|
+
st.write("Hello World")""",
|
|
256
|
+
"""--- a/app.py
|
|
257
|
+
+++ b/app.py
|
|
258
|
+
@@ -3,1 +3,1 @@
|
|
259
|
+
-st.title("Old App")
|
|
260
|
+
+st.title("New App")
|
|
261
|
+
@@ -4,1 +4,3 @@
|
|
262
|
+
st.write("Hello World")
|
|
263
|
+
+
|
|
264
|
+
+st.write("Footer text")""",
|
|
265
|
+
"""import streamlit as st
|
|
266
|
+
|
|
267
|
+
st.title("New App")
|
|
268
|
+
st.write("Hello World")
|
|
269
|
+
|
|
270
|
+
st.write("Footer text")"""
|
|
271
|
+
),
|
|
272
|
+
|
|
273
|
+
# Test case 12: Simple multiple hunks - remove and add lines
|
|
274
|
+
(
|
|
275
|
+
"""import streamlit as st
|
|
276
|
+
st.write("Remove this")
|
|
277
|
+
st.title("My App")""",
|
|
278
|
+
"""--- a/app.py
|
|
279
|
+
+++ b/app.py
|
|
280
|
+
@@ -2,1 +2,0 @@
|
|
281
|
+
-st.write("Remove this")
|
|
282
|
+
@@ -3,1 +3,2 @@
|
|
283
|
+
st.title("My App")
|
|
284
|
+
+st.write("Add this")""",
|
|
285
|
+
"""import streamlit as st
|
|
286
|
+
st.title("My App")
|
|
287
|
+
st.write("Add this")"""
|
|
288
|
+
),
|
|
289
|
+
|
|
290
|
+
# Test case 13: Multiple hunks with context lines
|
|
291
|
+
(
|
|
292
|
+
"""import streamlit as st
|
|
293
|
+
|
|
294
|
+
st.title("Old Title")
|
|
295
|
+
st.write("Some content")
|
|
296
|
+
|
|
297
|
+
st.write("Old message")""",
|
|
298
|
+
"""--- a/app.py
|
|
299
|
+
+++ b/app.py
|
|
300
|
+
@@ -3,1 +3,1 @@
|
|
301
|
+
-st.title("Old Title")
|
|
302
|
+
+st.title("New Title")
|
|
303
|
+
@@ -6,1 +6,1 @@
|
|
304
|
+
-st.write("Old message")
|
|
305
|
+
+st.write("New message")""",
|
|
306
|
+
"""import streamlit as st
|
|
307
|
+
|
|
308
|
+
st.title("New Title")
|
|
309
|
+
st.write("Some content")
|
|
310
|
+
|
|
311
|
+
st.write("New message")"""
|
|
312
|
+
),
|
|
313
|
+
|
|
314
|
+
# Test case 14: Multiple hunks - add imports and change content
|
|
315
|
+
(
|
|
316
|
+
"""import streamlit as st
|
|
317
|
+
|
|
318
|
+
st.title("My App")
|
|
319
|
+
st.write("Hello")
|
|
320
|
+
st.write("World")""",
|
|
321
|
+
"""--- a/app.py
|
|
322
|
+
+++ b/app.py
|
|
323
|
+
@@ -1,1 +1,3 @@
|
|
324
|
+
import streamlit as st
|
|
325
|
+
+import pandas as pd
|
|
326
|
+
+import numpy as np
|
|
327
|
+
@@ -4,2 +4,2 @@
|
|
328
|
+
st.write("Hello")
|
|
329
|
+
-st.write("World")
|
|
330
|
+
+st.write("World!")""",
|
|
331
|
+
"""import streamlit as st
|
|
332
|
+
import pandas as pd
|
|
333
|
+
import numpy as np
|
|
334
|
+
|
|
335
|
+
st.title("My App")
|
|
336
|
+
st.write("Hello")
|
|
337
|
+
st.write("World!")"""
|
|
338
|
+
),
|
|
339
|
+
|
|
340
|
+
# Test case 15: Add emoji to streamlit app title
|
|
341
|
+
(
|
|
342
|
+
"""import streamlit as st
|
|
343
|
+
|
|
344
|
+
st.title("My App")
|
|
345
|
+
st.write("Welcome to my application")""",
|
|
346
|
+
"""--- a/app.py
|
|
347
|
+
+++ b/app.py
|
|
348
|
+
@@ -3,1 +3,1 @@
|
|
349
|
+
-st.title("My App")
|
|
350
|
+
+st.title("🚀 My App")
|
|
351
|
+
|
|
352
|
+
st.write("Welcome to my application")""",
|
|
353
|
+
"""import streamlit as st
|
|
354
|
+
|
|
355
|
+
st.title("🚀 My App")
|
|
356
|
+
st.write("Welcome to my application")"""
|
|
357
|
+
)
|
|
358
|
+
])
|
|
359
|
+
def test_apply_patch_to_text(original_text, diff, expected_result):
|
|
360
|
+
"""Test the apply_patch_to_text function with various diff scenarios."""
|
|
361
|
+
result = apply_patch_to_text(original_text, diff)
|
|
362
|
+
|
|
363
|
+
print(f"Original text: {repr(original_text)}")
|
|
364
|
+
print(f"Diff: {repr(diff)}")
|
|
365
|
+
print(f"Expected result: {repr(expected_result)}")
|
|
366
|
+
print(f"Result: {repr(result)}")
|
|
367
|
+
|
|
368
|
+
assert result == expected_result
|