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.
Files changed (69) hide show
  1. mito_ai/__init__.py +10 -1
  2. mito_ai/_version.py +1 -1
  3. mito_ai/anthropic_client.py +90 -5
  4. mito_ai/chat_history/handlers.py +63 -0
  5. mito_ai/chat_history/urls.py +32 -0
  6. mito_ai/completions/handlers.py +18 -20
  7. mito_ai/constants.py +3 -0
  8. mito_ai/streamlit_conversion/agent_utils.py +148 -30
  9. mito_ai/streamlit_conversion/prompts/prompt_constants.py +147 -24
  10. mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +2 -1
  11. mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +2 -2
  12. mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +4 -3
  13. mito_ai/streamlit_conversion/prompts/update_existing_app_prompt.py +50 -0
  14. mito_ai/streamlit_conversion/streamlit_agent_handler.py +101 -107
  15. mito_ai/streamlit_conversion/streamlit_system_prompt.py +1 -0
  16. mito_ai/streamlit_conversion/streamlit_utils.py +13 -10
  17. mito_ai/streamlit_conversion/validate_streamlit_app.py +77 -82
  18. mito_ai/streamlit_preview/handlers.py +3 -4
  19. mito_ai/streamlit_preview/utils.py +11 -7
  20. mito_ai/tests/chat_history/test_chat_history.py +211 -0
  21. mito_ai/tests/message_history/test_message_history_utils.py +43 -19
  22. mito_ai/tests/providers/test_anthropic_client.py +178 -6
  23. mito_ai/tests/streamlit_conversion/test_apply_patch_to_text.py +368 -0
  24. mito_ai/tests/streamlit_conversion/test_fix_diff_headers.py +533 -0
  25. mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +71 -74
  26. mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +16 -16
  27. mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +17 -14
  28. mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +2 -2
  29. mito_ai/tests/user/__init__.py +2 -0
  30. mito_ai/tests/user/test_user.py +120 -0
  31. mito_ai/user/handlers.py +33 -0
  32. mito_ai/user/urls.py +21 -0
  33. mito_ai/utils/anthropic_utils.py +8 -6
  34. mito_ai/utils/message_history_utils.py +4 -3
  35. mito_ai/utils/telemetry_utils.py +7 -4
  36. {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +1 -1
  37. {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
  38. {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
  39. 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
  40. mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.20f12766ecd3d430568e.js.map +1 -0
  41. 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
  42. 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
  43. {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/METADATA +1 -1
  44. {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/RECORD +68 -58
  45. mito_ai-0.1.45.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.0c3368195d954d2ed033.js.map +0 -1
  46. {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
  47. {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
  48. {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
  49. {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
  50. {mito_ai-0.1.45.data → mito_ai-0.1.46.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
  51. {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
  52. {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
  53. {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
  54. {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
  55. {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
  56. {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
  57. {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
  58. {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
  59. {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
  60. {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
  61. {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
  62. {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
  63. {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
  64. {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
  65. {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
  66. {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
  67. {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/WHEEL +0 -0
  68. {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/entry_points.txt +0 -0
  69. {mito_ai-0.1.45.dist-info → mito_ai-0.1.46.dist-info}/licenses/LICENSE +0 -0
@@ -3,26 +3,100 @@
3
3
 
4
4
  MITO_TODO_PLACEHOLDER = "# MITO_TODO_PLACEHOLDER"
5
5
 
6
- unified_diff_instrucrions = f"""
6
+ unified_diff_instructions = f"""
7
7
  RESPONSE FORMAT: Return the changes you want to make to the streamlit app as a **unified diff (git-style patch)**:
8
- - Begin with a ````unified_diff` header and a ```` end header.
9
- - Then, include the standard header lines `--- a/app.py` and `+++ b/app.py`.
8
+
9
+ A unified diff looks is the following and tells the system which lines of code to add, remove, or modify:
10
+ --- a/app.py
11
+ +++ b/app.py
12
+ @@ -START_LINE,1 +START_LINE,1 @@
13
+ x = 1
14
+ -y = 2
15
+ +y = 3
16
+
17
+ The components of the unified diff are the following:
18
+ - `--- a/app.py` -> The original file. We will always use the file app.py
19
+ - `+++ b/app.py` -> The modified file. We will always use the file app.py
20
+ - `@@ -START_LINE,1 +START_LINE,1 @@` -> The hunk header
21
+ - `x = 1` -> The original context line
22
+ - `-y = 2` -> The removed line
23
+ - `+y = 3` -> The added line
24
+
25
+ When you create a unified diff, you must follow the following format:
26
+ - Begin with a ```unified_diff marker and a ``` end marker.
27
+ - Always, include the standard header. On line 1: `--- a/app.py` and on line 2: `+++ b/app.py` like in the example above.
10
28
  - Show only the modified hunks; each hunk must start with an `@@` header with line numbers.
11
29
  - Within each hunk:
12
- * Unchanged context lines start with a single space.
30
+ * Unchanged context lines start with a single space ` `.
13
31
  * Removed lines start with `-`.
14
32
  * Added lines start with `+`.
15
33
  - If there are **no changes**, return an empty string.
16
34
  - Do not include the line numbers in your response.
17
35
 
18
- **IMPORTANT: For the hunk header, use `@@ -START_LINE,1 +START_LINE,1 @@` where we always use 1 as the count value. In a later step, the system will automatically calculate the correct counts.**
36
+ **CRITICAL: INDENTATION HANDLING**
37
+ When modifying indented code (like content inside tabs, functions, or loops), you MUST:
38
+ - **Preserve exact indentation levels** in your added lines
39
+ - **Show the complete indentation change** when moving code between indentation levels
40
+ - **Include all whitespace** in your diff - indentation is part of the code structure
41
+ - When unindenting code (removing tabs/context), show the original indented line with `-` and the unindented version with `+`
42
+ - When indenting code (adding tabs/context), show the original unindented line with `-` and the indented version with `+`
43
+
44
+ **HUNK HEADER FORMAT:**
45
+ Use `@@ -START_LINE,1 +START_LINE,1 @@` where:
46
+ - START_LINE is the line number in the **original file** where this hunk begins
47
+ - Always use `1` for both count values (the system will calculate correct counts later)
48
+ - All line numbers must reference the **original file**, not the modified version
49
+ - For example, if the hunk begins on line 12, use `@@ -12,1 +12,1 @@`
50
+
51
+ **WRONG FORMATS (DO NOT USE):**
52
+ `@@ -12:` -> This is wrong because it is using a colon, doesn't have the count value, and doesn't have both sets of start_line numbers and lines counts.
53
+ `@@ -12,1` -> This is wrong because it doesn't have both sets of start_line numbers and lines counts.
54
+ `@@ 12,1 12,1 @@` -> This is wrong because it doesn't use - and + before the start_line
55
+ `@@-12,1 +12,1@@` -> This is wrong because it doesn't have a space after the first @@ and doesn't have a space before the second @@.
56
+
57
+ **MULTIPLE HUNKS:**
58
+ - If changes are separated by 5+ unchanged lines, create separate hunks
59
+ - Each hunk needs its own `@@` header with the correct START_LINE for that section
60
+ - Hunks must be in ascending order by line number
61
+
62
+ <Example 1: Single change in middle of file>
63
+
64
+ Assume `data_list = [` is on line 57 of the original file:
65
+ ```unified_diff
66
+ --- a/app.py
67
+ +++ b/app.py
68
+ @@ -57,1 +57,1 @@
69
+ data_list = [
70
+ - {{'id': 1, 'name': 'Old'}},
71
+ + {{'id': 1, 'name': 'New'}},
72
+ + {{'id': 2, 'name': 'Also New'}},
73
+ ```
74
+ </Example 1>
75
+
76
+ <Example 2: Multiple separate changes>
77
+ Assume the original file has:
78
+
79
+ Line 5: import os
80
+ Line 30: def process():
81
+
82
+ ```unified_diff
83
+ --- a/app.py
84
+ +++ b/app.py
85
+ @@ -5,1 +5,1 @@
86
+ import os
87
+ +import sys
88
+ @@ -30,1 +30,1 @@
89
+ -def process():
90
+ +def process_data():
91
+ ```
92
+ </Example 2>
19
93
 
20
- <Example Response>
94
+ <Example 3: Adding multiple entries to a list while respecting indentations>
21
95
 
22
96
  In the example below, assume that the line of code `data_list = [` is on line 57 of the existing streamlit app.
23
97
 
24
98
  ```unified_diff
25
- --- a/app.py
99
+ --- a/app.py
26
100
  +++ b/app.py
27
101
  @@ -57,1 +57,1 @@
28
102
  data_list = [
@@ -36,24 +110,73 @@ In the example below, assume that the line of code `data_list = [` is on line 57
36
110
  + {{'id': 7, 'name': 'Item G', 'category': 'Type 7', 'value': 700}},
37
111
  + {{'id': 8, 'name': 'Item H', 'category': 'Type 8', 'value': 800}},
38
112
  + {{'id': 9, 'name': 'Item I', 'category': 'Type 9', 'value': 900}},
39
- + {{'id': 10, 'name': 'Item J', 'category': 'Type 10', 'value': 1000}},
40
- + {{'id': 11, 'name': 'Item K', 'category': 'Type 11', 'value': 1100}},
41
- + {{'id': 12, 'name': 'Item L', 'category': 'Type 12', 'value': 1200}},
42
- + {{'id': 13, 'name': 'Item M', 'category': 'Type 13', 'value': 1300}},
43
- + {{'id': 14, 'name': 'Item N', 'category': 'Type 14', 'value': 1400}},
44
- + {{'id': 15, 'name': 'Item O', 'category': 'Type 15', 'value': 1500}},
45
- + {{'id': 16, 'name': 'Item P', 'category': 'Type 16', 'value': 1600}},
46
- + {{'id': 17, 'name': 'Item Q', 'category': 'Type 17', 'value': 1700}},
47
- + {{'id': 18, 'name': 'Item R', 'category': 'Type 18', 'value': 1800}},
48
- + {{'id': 19, 'name': 'Item S', 'category': 'Type 19', 'value': 1900}},
49
- + {{'id': 20, 'name': 'Item T', 'category': 'Type 20', 'value': 2000}},
50
- + {{'id': 21, 'name': 'Item U', 'category': 'Type 21', 'value': 2100}},
51
- + {{'id': 22, 'name': 'Item V', 'category': 'Type 22', 'value': 2200}},
52
- + {{'id': 23, 'name': 'Item W', 'category': 'Type 23', 'value': 2300}},
53
- + {{'id': 24, 'name': 'Item X', 'category': 'Type 24', 'value': 2400}},
54
- + {{'id': 25, 'name': 'Item Y', 'category': 'Type 25', 'value': 2500}}
113
+ + {{'id': 10, 'name': 'Item J', 'category': 'Type 10', 'value': 1000}}
114
+ ```
115
+ </Example 3>
116
+
117
+ <Example 4: Consolidating tabs - removing tab structure and unindenting content>
118
+
119
+ Assume the original file has tabs starting at line 10:
120
+ ```python
121
+ tab1, tab2 = st.tabs(["Cat", "Dog"])
122
+
123
+ with tab1:
124
+ st.header("A cat")
125
+ st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
126
+ with tab2:
127
+ st.header("A dog")
128
+ st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
129
+ ```
130
+
131
+ To consolidate into a single screen without tabs:
132
+ ```unified_diff
133
+ --- a/app.py
134
+ +++ b/app.py
135
+ @@ -10,1 +10,1 @@
136
+ -tab1, tab2 = st.tabs(["Cat", "Dog"])
137
+ -
138
+ -with tab1:
139
+ - st.header("A cat")
140
+ - st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
141
+ -with tab2:
142
+ - st.header("A dog")
143
+ - st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
144
+ +st.header("A cat")
145
+ +st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
146
+ +st.header("A dog")
147
+ +st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
148
+ ```
149
+ </Example 4>
150
+
151
+ <Example 5: Adding tab structure - indenting existing content>
152
+
153
+ Assume the original file has content starting at line 10:
154
+ ```python
155
+ st.header("A cat")
156
+ st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
157
+ st.header("A dog")
158
+ st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
159
+ ```
160
+
161
+ To add tab structure:
162
+ ```unified_diff
163
+ --- a/app.py
164
+ +++ b/app.py
165
+ @@ -10,1 +10,1 @@
166
+ -st.header("A cat")
167
+ -st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
168
+ -st.header("A dog")
169
+ -st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
170
+ +tab1, tab2 = st.tabs(["Cat", "Dog"])
171
+ +
172
+ +with tab1:
173
+ + st.header("A cat")
174
+ + st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
175
+ +with tab2:
176
+ + st.header("A dog")
177
+ + st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
55
178
  ```
56
- </Example Response>
179
+ </Example 5>
57
180
 
58
181
  Your response must consist **only** of valid unified-diff block.
59
182
  """
@@ -1,9 +1,10 @@
1
1
  # Copyright (c) Saga Inc.
2
2
  # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
3
 
4
+ from typing import List
4
5
  from mito_ai.streamlit_conversion.prompts.prompt_constants import MITO_TODO_PLACEHOLDER
5
6
 
6
- def get_streamlit_app_creation_prompt(notebook: dict) -> str:
7
+ def get_streamlit_app_creation_prompt(notebook: List[dict]) -> str:
7
8
  """
8
9
  This prompt is used to create a streamlit app from a notebook.
9
10
  """
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) Saga Inc.
2
2
  # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
3
 
4
- from mito_ai.streamlit_conversion.prompts.prompt_constants import unified_diff_instrucrions
4
+ from mito_ai.streamlit_conversion.prompts.prompt_constants import unified_diff_instructions
5
5
  from mito_ai.streamlit_conversion.prompts.prompt_utils import add_line_numbers_to_code
6
6
 
7
7
  def get_streamlit_error_correction_prompt(error: str, streamlit_app_code: str) -> str:
@@ -12,7 +12,7 @@ def get_streamlit_error_correction_prompt(error: str, streamlit_app_code: str) -
12
12
 
13
13
  Your job is to fix the error now. Only fix the specific error that you are instructed to fix now. Do not fix other error that that you anticipate. You will be asked to fix other errors later.
14
14
 
15
- {unified_diff_instrucrions}
15
+ {unified_diff_instructions}
16
16
 
17
17
  ===============================================
18
18
 
@@ -1,10 +1,11 @@
1
1
  # Copyright (c) Saga Inc.
2
2
  # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
3
 
4
- from mito_ai.streamlit_conversion.prompts.prompt_constants import MITO_TODO_PLACEHOLDER, unified_diff_instrucrions
4
+ from typing import List
5
+ from mito_ai.streamlit_conversion.prompts.prompt_constants import MITO_TODO_PLACEHOLDER, unified_diff_instructions
5
6
  from mito_ai.streamlit_conversion.prompts.prompt_utils import add_line_numbers_to_code
6
7
 
7
- def get_finish_todo_prompt(notebook: dict, existing_streamlit_app_code: str, todo_placeholder: str) -> str:
8
+ def get_finish_todo_prompt(notebook: List[dict], existing_streamlit_app_code: str, todo_placeholder: str) -> str:
8
9
 
9
10
  existing_streamlit_app_code_with_line_numbers = add_line_numbers_to_code(existing_streamlit_app_code)
10
11
 
@@ -24,7 +25,7 @@ You have ONE and ONLY ONE opportunity to complete this TODO. If you do not finis
24
25
  - If creating functions: Implement ALL required functionality
25
26
  - If converting a visualization: Copy over ALL of the visualization code from the notebook, including all styling and formatting.
26
27
 
27
- {unified_diff_instrucrions}
28
+ {unified_diff_instructions}
28
29
 
29
30
  ===============================================
30
31
 
@@ -0,0 +1,50 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from typing import List
5
+ from mito_ai.streamlit_conversion.prompts.prompt_constants import unified_diff_instructions
6
+ from mito_ai.streamlit_conversion.prompts.prompt_utils import add_line_numbers_to_code
7
+
8
+ def get_update_existing_app_prompt(notebook: List[dict], streamlit_app_code: str, edit_prompt: str) -> str:
9
+ """
10
+ This prompt is used to update an existing streamlit app.
11
+ """
12
+
13
+ existing_streamlit_app_code_with_line_numbers = add_line_numbers_to_code(streamlit_app_code)
14
+
15
+ return f"""
16
+
17
+ GOAL: You've previously created a first draft of the Streamlit app. Now the user reviewed it and provided feedback.Update the existing streamlit app according to the feedback provided by the user. Use the input notebook to help you understand what code needs to be added, changed, or modified to fulfill the user's edit request.
18
+
19
+ **CRITICAL COMPLETION REQUIREMENT:**
20
+ You have ONE and ONLY ONE opportunity to complete this edit request. If you do not finish the entire task completely, the application will be broken and unusable. This is your final chance to get it right.
21
+
22
+ **COMPLETION RULES:**
23
+ 1. **NEVER leave partial work** - If the edit request requires generating a list with 100 items, provide ALL 100 items.
24
+ 2. **NEVER use placeholders** - This is your only opportunity to fulfill this edit request, so do not leave yourself another TODOs.
25
+ 3. **NEVER assume "good enough"** - Complete the task to 100% satisfaction.
26
+ 4. **If the task seems large, that's exactly why it needs to be done now** - This is your only chance
27
+
28
+ **HOW TO DETERMINE IF TASK IS COMPLETE:**
29
+ - If building a list/dictionary: Include ALL items that should be in the final data structure.
30
+ - If creating functions: Implement ALL required functionality.
31
+ - If converting a visualization: Copy over ALL of the visualization code from the notebook, including all styling and formatting.
32
+
33
+ {unified_diff_instructions}
34
+
35
+ ===============================================
36
+
37
+ INPUT NOTEBOOK:
38
+ {notebook}
39
+
40
+ ===============================================
41
+
42
+ EXISTING STREAMLIT APP:
43
+ {existing_streamlit_app_code_with_line_numbers}
44
+
45
+ ===============================================
46
+
47
+ USER EDIT REQUEST:
48
+ {edit_prompt}
49
+
50
+ """
@@ -1,137 +1,138 @@
1
1
  # Copyright (c) Saga Inc.
2
2
  # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
3
 
4
- import logging
5
4
  import os
6
5
  from anthropic.types import MessageParam
7
6
  from typing import List, Optional, Tuple, cast
8
-
9
- from mito_ai.logger import get_logger
10
- from mito_ai.streamlit_conversion.agent_utils import apply_patch_to_text, extract_todo_placeholders, fix_diff_headers
7
+ from mito_ai.streamlit_conversion.agent_utils import apply_patch_to_text, extract_todo_placeholders, fix_diff_headers, get_response_from_agent
11
8
  from mito_ai.streamlit_conversion.prompts.streamlit_app_creation_prompt import get_streamlit_app_creation_prompt
12
9
  from mito_ai.streamlit_conversion.prompts.streamlit_error_correction_prompt import get_streamlit_error_correction_prompt
13
10
  from mito_ai.streamlit_conversion.prompts.streamlit_finish_todo_prompt import get_finish_todo_prompt
14
- from mito_ai.streamlit_conversion.streamlit_system_prompt import streamlit_system_prompt
11
+ from mito_ai.streamlit_conversion.prompts.update_existing_app_prompt import get_update_existing_app_prompt
15
12
  from mito_ai.streamlit_conversion.validate_streamlit_app import validate_app
16
- from mito_ai.streamlit_conversion.streamlit_utils import extract_code_blocks, create_app_file, extract_unified_diff_blocks, parse_jupyter_notebook_to_extract_required_content
17
- from mito_ai.utils.anthropic_utils import stream_anthropic_completion_from_mito_server
13
+ from mito_ai.streamlit_conversion.streamlit_utils import extract_code_blocks, create_app_file, extract_unified_diff_blocks, get_app_code_from_file, parse_jupyter_notebook_to_extract_required_content
18
14
  from mito_ai.completions.models import MessageType
19
15
  from mito_ai.utils.telemetry_utils import log_streamlit_app_creation_error, log_streamlit_app_creation_retry, log_streamlit_app_creation_success
20
16
  from mito_ai.streamlit_conversion.streamlit_utils import clean_directory_check
21
17
 
22
- STREAMLIT_AI_MODEL = "claude-3-5-haiku-latest"
23
-
24
- class StreamlitCodeGeneration:
25
- @property
26
- def log(self) -> logging.Logger:
27
- """Use Mito AI logger."""
28
- return get_logger()
29
-
30
- async def get_response_from_agent(self, message_to_agent: List[MessageParam]) -> str:
31
- """Gets the streaming response from the agent using the mito server"""
32
- model = STREAMLIT_AI_MODEL
33
- max_tokens = 8192 # 64_000
34
- temperature = 0.2
35
-
36
- self.log.info("Getting response from agent...")
37
- accumulated_response = ""
38
- async for stream_chunk in stream_anthropic_completion_from_mito_server(
39
- model = model,
40
- max_tokens = max_tokens,
41
- temperature = temperature,
42
- system = streamlit_system_prompt,
43
- messages = message_to_agent,
44
- stream=True,
45
- message_type=MessageType.STREAMLIT_CONVERSION,
46
- reply_fn=None,
47
- message_id=""
48
- ):
49
- accumulated_response += stream_chunk
50
- return accumulated_response
51
-
52
- async def generate_streamlit_code(self, notebook: dict) -> str:
53
- """Send a query to the agent, get its response and parse the code"""
54
-
55
- prompt_text = get_streamlit_app_creation_prompt(notebook)
56
-
57
- messages: List[MessageParam] = [
58
- cast(MessageParam, {
59
- "role": "user",
60
- "content": [{
61
- "type": "text",
62
- "text": prompt_text
63
- }]
64
- })
65
- ]
66
-
67
- agent_response = await self.get_response_from_agent(messages)
68
- converted_code = extract_code_blocks(agent_response)
69
-
70
- # Extract the TODOs from the agent's response
71
- todo_placeholders = extract_todo_placeholders(agent_response)
72
-
73
- for todo_placeholder in todo_placeholders:
74
- print(f"Processing AI TODO: {todo_placeholder}")
75
- todo_prompt = get_finish_todo_prompt(notebook, converted_code, todo_placeholder)
76
- todo_messages: List[MessageParam] = [
77
- cast(MessageParam, {
78
- "role": "user",
79
- "content": [{
80
- "type": "text",
81
- "text": todo_prompt
82
- }]
83
- })
84
- ]
85
- todo_response = await self.get_response_from_agent(todo_messages)
86
-
87
- # Apply the diff to the streamlit app
88
- exctracted_diff = extract_unified_diff_blocks(todo_response)
89
- fixed_diff = fix_diff_headers(exctracted_diff)
90
- converted_code = apply_patch_to_text(converted_code, fixed_diff)
91
-
92
- return converted_code
93
-
18
+ def get_app_directory(notebook_path: str) -> str:
19
+ # Make sure the path is absolute if it is not already
20
+ absolute_notebook_path = os.path.abspath(notebook_path)
21
+
22
+ # Get the directory of the notebook
23
+ app_directory = os.path.dirname(absolute_notebook_path)
24
+ return app_directory
94
25
 
95
- async def correct_error_in_generation(self, error: str, streamlit_app_code: str) -> str:
96
- """If errors are present, send it back to the agent to get corrections in code"""
97
- messages: List[MessageParam] = [
26
+ async def generate_new_streamlit_code(notebook: List[dict]) -> str:
27
+ """Send a query to the agent, get its response and parse the code"""
28
+
29
+ prompt_text = get_streamlit_app_creation_prompt(notebook)
30
+
31
+ messages: List[MessageParam] = [
32
+ cast(MessageParam, {
33
+ "role": "user",
34
+ "content": [{
35
+ "type": "text",
36
+ "text": prompt_text
37
+ }]
38
+ })
39
+ ]
40
+ agent_response = await get_response_from_agent(messages)
41
+ converted_code = extract_code_blocks(agent_response)
42
+
43
+ # Extract the TODOs from the agent's response
44
+ todo_placeholders = extract_todo_placeholders(agent_response)
45
+
46
+ for todo_placeholder in todo_placeholders:
47
+ print(f"Processing AI TODO: {todo_placeholder}")
48
+ todo_prompt = get_finish_todo_prompt(notebook, converted_code, todo_placeholder)
49
+ todo_messages: List[MessageParam] = [
98
50
  cast(MessageParam, {
99
51
  "role": "user",
100
52
  "content": [{
101
53
  "type": "text",
102
- "text": get_streamlit_error_correction_prompt(error, streamlit_app_code)
54
+ "text": todo_prompt
103
55
  }]
104
56
  })
105
57
  ]
106
- agent_response = await self.get_response_from_agent(messages)
58
+ todo_response = await get_response_from_agent(todo_messages)
107
59
 
108
60
  # Apply the diff to the streamlit app
109
- exctracted_diff = extract_unified_diff_blocks(agent_response)
110
-
111
- print(f"\n\nExtracted diff: {exctracted_diff}")
61
+ exctracted_diff = extract_unified_diff_blocks(todo_response)
112
62
  fixed_diff = fix_diff_headers(exctracted_diff)
113
- streamlit_app_code = apply_patch_to_text(streamlit_app_code, fixed_diff)
114
-
115
- print("\n\nUpdated app code: ", streamlit_app_code)
63
+ converted_code = apply_patch_to_text(converted_code, fixed_diff)
64
+
65
+ return converted_code
116
66
 
117
- return streamlit_app_code
118
67
 
68
+ async def update_existing_streamlit_code(notebook: List[dict], streamlit_app_code: str, edit_prompt: str) -> str:
69
+ """Send a query to the agent, get its response and parse the code"""
70
+ prompt_text = get_update_existing_app_prompt(notebook, streamlit_app_code, edit_prompt)
71
+
72
+ messages: List[MessageParam] = [
73
+ cast(MessageParam, {
74
+ "role": "user",
75
+ "content": [{
76
+ "type": "text",
77
+ "text": prompt_text
78
+ }]
79
+ })
80
+ ]
81
+
82
+ agent_response = await get_response_from_agent(messages)
83
+ exctracted_diff = extract_unified_diff_blocks(agent_response)
84
+ fixed_diff = fix_diff_headers(exctracted_diff)
85
+ print(fixed_diff)
86
+ converted_code = apply_patch_to_text(streamlit_app_code, fixed_diff)
87
+ return converted_code
88
+
89
+
90
+ async def correct_error_in_generation(error: str, streamlit_app_code: str) -> str:
91
+ """If errors are present, send it back to the agent to get corrections in code"""
92
+ messages: List[MessageParam] = [
93
+ cast(MessageParam, {
94
+ "role": "user",
95
+ "content": [{
96
+ "type": "text",
97
+ "text": get_streamlit_error_correction_prompt(error, streamlit_app_code)
98
+ }]
99
+ })
100
+ ]
101
+ agent_response = await get_response_from_agent(messages)
102
+
103
+ # Apply the diff to the streamlit app
104
+ exctracted_diff = extract_unified_diff_blocks(agent_response)
105
+ fixed_diff = fix_diff_headers(exctracted_diff)
106
+ streamlit_app_code = apply_patch_to_text(streamlit_app_code, fixed_diff)
107
+
108
+ return streamlit_app_code
119
109
 
120
- async def streamlit_handler(notebook_path: str) -> Tuple[bool, Optional[str], str]:
110
+ async def streamlit_handler(notebook_path: str, edit_prompt: str = "") -> Tuple[bool, Optional[str], str]:
121
111
  """Handler function for streamlit code generation and validation"""
122
112
 
123
113
  clean_directory_check(notebook_path)
124
114
 
125
115
  notebook_code = parse_jupyter_notebook_to_extract_required_content(notebook_path)
126
- streamlit_code_generator = StreamlitCodeGeneration()
127
-
128
- streamlit_code = await streamlit_code_generator.generate_streamlit_code(notebook_code)
116
+ app_directory = get_app_directory(notebook_path)
129
117
 
118
+ if edit_prompt != "":
119
+ # If the user is editing an existing streamlit app, use the update function
120
+ streamlit_code = get_app_code_from_file(app_directory)
121
+
122
+ if streamlit_code is None:
123
+ return False, '', "Error updating existing streamlit app because app.py file was not found."
124
+
125
+ streamlit_code = await update_existing_streamlit_code(notebook_code, streamlit_code, edit_prompt)
126
+ else:
127
+ # Otherwise generate a new streamlit app
128
+ streamlit_code = await generate_new_streamlit_code(notebook_code)
129
+
130
+ # Then, after creating/updating the app, validate that the new code runs
130
131
  has_validation_error, errors = validate_app(streamlit_code, notebook_path)
131
132
  tries = 0
132
133
  while has_validation_error and tries < 5:
133
134
  for error in errors:
134
- streamlit_code = await streamlit_code_generator.correct_error_in_generation(error, streamlit_code)
135
+ streamlit_code = await correct_error_in_generation(error, streamlit_code)
135
136
 
136
137
  has_validation_error, errors = validate_app(streamlit_code, notebook_path)
137
138
 
@@ -142,20 +143,13 @@ async def streamlit_handler(notebook_path: str) -> Tuple[bool, Optional[str], st
142
143
  tries+=1
143
144
 
144
145
  if has_validation_error:
145
- log_streamlit_app_creation_error('mito_server_key', MessageType.STREAMLIT_CONVERSION, error)
146
+ log_streamlit_app_creation_error('mito_server_key', MessageType.STREAMLIT_CONVERSION, error, edit_prompt)
146
147
  return False, '', "Error generating streamlit code by agent"
147
148
 
148
- # Convert to absolute path for directory calculation
149
- absolute_notebook_path = notebook_path
150
- if not (notebook_path.startswith('/') or (len(notebook_path) > 1 and notebook_path[1] == ':')):
151
- absolute_notebook_path = os.path.join(os.getcwd(), notebook_path)
152
-
153
- app_directory = os.path.dirname(absolute_notebook_path)
154
-
149
+ # Finally, update the app.py file with the new code
155
150
  success_flag, app_path, message = create_app_file(app_directory, streamlit_code)
156
-
157
151
  if not success_flag:
158
- log_streamlit_app_creation_error('mito_server_key', MessageType.STREAMLIT_CONVERSION, message)
152
+ log_streamlit_app_creation_error('mito_server_key', MessageType.STREAMLIT_CONVERSION, message, edit_prompt)
159
153
 
160
- log_streamlit_app_creation_success('mito_server_key', MessageType.STREAMLIT_CONVERSION)
154
+ log_streamlit_app_creation_success('mito_server_key', MessageType.STREAMLIT_CONVERSION, edit_prompt)
161
155
  return success_flag, app_path, message
@@ -22,6 +22,7 @@ STREAMLIT IMPLEMENTATION GUIDELINES:
22
22
  - Include all text explanations and insights from markdown cells
23
23
  - Add interactive elements where beneficial (filters, selectors, etc.)
24
24
  - Ensure professional styling and layout suitable for executives
25
+ - Just create the streamlit app code, do not include a _main_ function block. The file will be run directly using `streamlit run app.py`.
25
26
 
26
27
  CRITICAL REQUIREMENTS:
27
28
  1. **PRESERVE ALL CODE EXACTLY**: Every line of code, every data structure, every import must be included in full
@@ -4,7 +4,7 @@
4
4
  import re
5
5
  import json
6
6
  import os
7
- from typing import Dict, Optional, Tuple, Any
7
+ from typing import Dict, List, Optional, Tuple, Any
8
8
  from pathlib import Path
9
9
 
10
10
  def extract_code_blocks(message_content: str) -> str:
@@ -64,6 +64,13 @@ def create_app_file(app_directory: str, code: str) -> Tuple[bool, str, str]:
64
64
  except Exception as e:
65
65
  return False, '', f"Unexpected error: {str(e)}"
66
66
 
67
+ def get_app_code_from_file(app_directory: str) -> Optional[str]:
68
+ app_path = get_app_path(app_directory)
69
+ if app_path is None:
70
+ return None
71
+ with open(app_path, 'r', encoding='utf-8') as f:
72
+ return f.read()
73
+
67
74
 
68
75
  def get_app_path(app_directory: str) -> Optional[str]:
69
76
  """
@@ -73,9 +80,8 @@ def get_app_path(app_directory: str) -> Optional[str]:
73
80
  if not os.path.exists(app_path):
74
81
  return None
75
82
  return app_path
76
-
77
83
 
78
- def parse_jupyter_notebook_to_extract_required_content(notebook_path: str) -> Dict[str, Any]:
84
+ def parse_jupyter_notebook_to_extract_required_content(notebook_path: str) -> List[Dict[str, Any]]:
79
85
  """
80
86
  Read a Jupyter notebook and filter cells to keep only cell_type and source fields.
81
87
 
@@ -105,18 +111,15 @@ def parse_jupyter_notebook_to_extract_required_content(notebook_path: str) -> Di
105
111
  raise KeyError("Notebook does not contain 'cells' key")
106
112
 
107
113
  # Filter each cell to keep only cell_type and source
108
- filtered_cells = []
114
+ filtered_cells: List[Dict[str, Any]] = []
109
115
  for cell in notebook_data['cells']:
110
- filtered_cell = {
116
+ filtered_cell: Dict[str, Any] = {
111
117
  'cell_type': cell.get('cell_type', ''),
112
118
  'source': cell.get('source', [])
113
119
  }
114
120
  filtered_cells.append(filtered_cell)
115
-
116
- # Update the notebook data with filtered cells
117
- notebook_data['cells'] = filtered_cells
118
-
119
- return notebook_data
121
+
122
+ return filtered_cells
120
123
 
121
124
  except FileNotFoundError:
122
125
  raise FileNotFoundError(f"Notebook file not found: {notebook_path}")