awacke1 commited on
Commit
e8aa493
β€’
1 Parent(s): 06301e4

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +388 -0
app.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from azure.cosmos import CosmosClient, exceptions
3
+ import os
4
+ import pandas as pd
5
+ import traceback
6
+ import shutil
7
+ from github import Github
8
+ from git import Repo
9
+ from datetime import datetime
10
+ import base64
11
+ import json
12
+ import uuid # 🎲 For generating unique IDs
13
+
14
+
15
+ # πŸŽ‰ Welcome to our fun-filled Cosmos DB and GitHub Integration app!
16
+ st.set_page_config(layout="wide")
17
+
18
+ # 🌌 Cosmos DB configuration
19
+ ENDPOINT = "https://acae-afd.documents.azure.com:443/"
20
+ DATABASE_NAME = os.environ.get("COSMOS_DATABASE_NAME")
21
+ CONTAINER_NAME = os.environ.get("COSMOS_CONTAINER_NAME")
22
+ Key = os.environ.get("Key") # πŸ”‘ Don't forget your key!
23
+
24
+ # πŸ™ GitHub configuration
25
+ def download_github_repo(url, local_path):
26
+ # 🚚 Let's download that GitHub repo!
27
+ if os.path.exists(local_path):
28
+ shutil.rmtree(local_path)
29
+ Repo.clone_from(url, local_path)
30
+
31
+ def create_zip_file(source_dir, output_filename):
32
+ # πŸ“¦ Zipping up files like a pro!
33
+ shutil.make_archive(output_filename, 'zip', source_dir)
34
+
35
+ def create_repo(g, repo_name):
36
+ # πŸ› οΈ Creating a new GitHub repo. Magic!
37
+ user = g.get_user()
38
+ return user.create_repo(repo_name)
39
+
40
+ def push_to_github(local_path, repo, github_token):
41
+ # πŸš€ Pushing code to GitHub. Hold on tight!
42
+ repo_url = f"https://{github_token}@github.com/{repo.full_name}.git"
43
+ local_repo = Repo(local_path)
44
+
45
+ if 'origin' in [remote.name for remote in local_repo.remotes]:
46
+ origin = local_repo.remote('origin')
47
+ origin.set_url(repo_url)
48
+ else:
49
+ origin = local_repo.create_remote('origin', repo_url)
50
+
51
+ if not local_repo.heads:
52
+ local_repo.git.checkout('-b', 'main')
53
+ current_branch = 'main'
54
+ else:
55
+ current_branch = local_repo.active_branch.name
56
+
57
+ local_repo.git.add(A=True)
58
+
59
+ if local_repo.is_dirty():
60
+ local_repo.git.commit('-m', 'Initial commit')
61
+
62
+ origin.push(refspec=f'{current_branch}:{current_branch}')
63
+
64
+ def get_base64_download_link(file_path, file_name):
65
+ # πŸ§™β€β™‚οΈ Generating a magical download link!
66
+ with open(file_path, "rb") as file:
67
+ contents = file.read()
68
+ base64_encoded = base64.b64encode(contents).decode()
69
+ return f'<a href="data:application/zip;base64,{base64_encoded}" download="{file_name}">⬇️ Download {file_name}</a>'
70
+
71
+
72
+ # 🧭 New functions for dynamic sidebar navigation
73
+ def get_databases(client):
74
+ # πŸ“š Fetching list of databases. So many options!
75
+ return [db['id'] for db in client.list_databases()]
76
+
77
+ def get_containers(database):
78
+ # πŸ“‚ Getting containers. Containers within containers!
79
+ return [container['id'] for container in database.list_containers()]
80
+
81
+ def get_documents(container, limit=None):
82
+ # πŸ“ Retrieving documents. Shhh, don't tell anyone!
83
+ query = "SELECT * FROM c ORDER BY c._ts DESC"
84
+ items = list(container.query_items(query=query, enable_cross_partition_query=True, max_item_count=limit))
85
+ return items
86
+
87
+
88
+ # 🌟 Cosmos DB functions
89
+ def insert_record(container, record):
90
+ try:
91
+ container.create_item(body=record)
92
+ return True, "Record inserted successfully! πŸŽ‰"
93
+ except exceptions.CosmosHttpResponseError as e:
94
+ return False, f"HTTP error occurred: {str(e)} 🚨"
95
+ except Exception as e:
96
+ return False, f"An unexpected error occurred: {str(e)} 😱"
97
+
98
+ def update_record(container, updated_record):
99
+ try:
100
+ container.upsert_item(body=updated_record)
101
+ return True, f"Record with id {updated_record['id']} successfully updated. πŸ› οΈ"
102
+ except exceptions.CosmosHttpResponseError as e:
103
+ return False, f"HTTP error occurred: {str(e)} 🚨"
104
+ except Exception as e:
105
+ return False, f"An unexpected error occurred: {traceback.format_exc()} 😱"
106
+
107
+ def delete_record(container, name, id):
108
+ try:
109
+ container.delete_item(item=id, partition_key=id)
110
+ return True, f"Successfully deleted record with name: {name} and id: {id} πŸ—‘οΈ"
111
+ except exceptions.CosmosResourceNotFoundError:
112
+ return False, f"Record with id {id} not found. It may have been already deleted. πŸ•΅οΈβ€β™‚οΈ"
113
+ except exceptions.CosmosHttpResponseError as e:
114
+ return False, f"HTTP error occurred: {str(e)} 🚨"
115
+ except Exception as e:
116
+ return False, f"An unexpected error occurred: {traceback.format_exc()} 😱"
117
+
118
+ # 🎲 Function to generate a unique UUID
119
+ def generate_unique_id():
120
+ # πŸ§™β€β™‚οΈ Generating a unique UUID!
121
+ return str(uuid.uuid4())
122
+
123
+ # πŸ“¦ Function to archive current container
124
+ def archive_current_container(database_name, container_name, client):
125
+ try:
126
+ base_dir = "./cosmos_archive_current_container"
127
+ if os.path.exists(base_dir):
128
+ shutil.rmtree(base_dir)
129
+ os.makedirs(base_dir)
130
+
131
+ db_client = client.get_database_client(database_name)
132
+ container_client = db_client.get_container_client(container_name)
133
+ items = list(container_client.read_all_items())
134
+
135
+ container_dir = os.path.join(base_dir, container_name)
136
+ os.makedirs(container_dir)
137
+
138
+ for item in items:
139
+ item_id = item.get('id', f"unknown_{datetime.now().strftime('%Y%m%d%H%M%S')}")
140
+ with open(os.path.join(container_dir, f"{item_id}.json"), 'w') as f:
141
+ json.dump(item, f, indent=2)
142
+
143
+ archive_name = f"{container_name}_archive_{datetime.now().strftime('%Y%m%d%H%M%S')}"
144
+ shutil.make_archive(archive_name, 'zip', base_dir)
145
+
146
+ return get_base64_download_link(f"{archive_name}.zip", f"{archive_name}.zip")
147
+ except Exception as e:
148
+ return f"An error occurred while archiving data: {str(e)} 😒"
149
+
150
+
151
+ # 🎈 Let's modify the main app to be more fun!
152
+ def main():
153
+ st.title("πŸ™Git🌌CosmosπŸ’« - Azure Cosmos DB and Github Agent")
154
+
155
+ # 🚦 Initialize session state
156
+ if 'logged_in' not in st.session_state:
157
+ st.session_state.logged_in = False
158
+ if 'selected_records' not in st.session_state:
159
+ st.session_state.selected_records = []
160
+ if 'client' not in st.session_state:
161
+ st.session_state.client = None
162
+ if 'selected_database' not in st.session_state:
163
+ st.session_state.selected_database = None
164
+ if 'selected_container' not in st.session_state:
165
+ st.session_state.selected_container = None
166
+ if 'selected_document_id' not in st.session_state:
167
+ st.session_state.selected_document_id = None
168
+
169
+ # πŸ” Automatic Login
170
+ if Key:
171
+ st.session_state.primary_key = Key
172
+ st.session_state.logged_in = True
173
+ else:
174
+ st.error("Cosmos DB Key is not set in environment variables. πŸ”‘βŒ")
175
+ return # Can't proceed without a key
176
+
177
+ if st.session_state.logged_in:
178
+ # 🌌 Initialize Cosmos DB client
179
+ try:
180
+ if st.session_state.client is None:
181
+ st.session_state.client = CosmosClient(ENDPOINT, credential=st.session_state.primary_key)
182
+
183
+ # πŸ—„οΈ Sidebar for database, container, and document selection
184
+ st.sidebar.title("πŸ™Git🌌CosmosπŸ’«πŸ—„οΈNavigator")
185
+
186
+ databases = get_databases(st.session_state.client)
187
+ selected_db = st.sidebar.selectbox("πŸ—ƒοΈ Select Database", databases)
188
+
189
+ if selected_db != st.session_state.selected_database:
190
+ st.session_state.selected_database = selected_db
191
+ st.session_state.selected_container = None
192
+ st.session_state.selected_document_id = None
193
+ st.experimental_rerun()
194
+
195
+ if st.session_state.selected_database:
196
+ database = st.session_state.client.get_database_client(st.session_state.selected_database)
197
+ containers = get_containers(database)
198
+ selected_container = st.sidebar.selectbox("πŸ“ Select Container", containers)
199
+
200
+ if selected_container != st.session_state.selected_container:
201
+ st.session_state.selected_container = selected_container
202
+ st.session_state.selected_document_id = None
203
+ st.experimental_rerun()
204
+
205
+ if st.session_state.selected_container:
206
+ container = database.get_container_client(st.session_state.selected_container)
207
+
208
+ # πŸ“¦ Add Export button
209
+ if st.button("πŸ“¦ Export Container Data"):
210
+ download_link = archive_current_container(st.session_state.selected_database, st.session_state.selected_container, st.session_state.client)
211
+ if download_link.startswith('<a'):
212
+ st.markdown(download_link, unsafe_allow_html=True)
213
+ else:
214
+ st.error(download_link)
215
+
216
+ # Fetch up to 6 documents to check if more than 5 exist
217
+ documents = get_documents(container, limit=6)
218
+
219
+ if len(documents) > 5:
220
+ documents_to_display = documents[:5]
221
+ st.info("Showing top 5 most recent documents.")
222
+ else:
223
+ documents_to_display = documents
224
+ st.info(f"Showing all {len(documents_to_display)} documents.")
225
+
226
+ if documents_to_display:
227
+ # 🎨 Add Viewer/Editor selection
228
+ view_options = ['Show as Markdown', 'Show as Code Editor', 'Show as Edit and Save', 'Clone Document', 'New Record']
229
+ selected_view = st.selectbox("Select Viewer/Editor", view_options, index=2)
230
+
231
+ if selected_view == 'Show as Edit and Save':
232
+ # ✏️ Show as Edit and Save
233
+ st.markdown("#### Edit the document fields below:")
234
+
235
+ # Create columns for each document
236
+ num_cols = len(documents_to_display)
237
+ cols = st.columns(num_cols)
238
+
239
+ for idx, (col, doc) in enumerate(zip(cols, documents_to_display)):
240
+ with col:
241
+ st.markdown(f"##### Document ID: {doc.get('id', '')}")
242
+ editable_id = st.text_input("ID", value=doc.get('id', ''), key=f'edit_id_{idx}')
243
+ # Remove 'id' from the document for editing other fields
244
+ editable_doc = doc.copy()
245
+ editable_doc.pop('id', None)
246
+ doc_str = st.text_area("Document Content (in JSON format)", value=json.dumps(editable_doc, indent=2), height=300, key=f'doc_str_{idx}')
247
+ if st.button("πŸ’Ύ Save Changes", key=f'save_button_{idx}'):
248
+ try:
249
+ updated_doc = json.loads(doc_str)
250
+ updated_doc['id'] = editable_id # Include the possibly edited ID
251
+ success, message = update_record(container, updated_doc)
252
+ if success:
253
+ st.success(f"Document {updated_doc['id']} saved successfully.")
254
+ st.session_state.selected_document_id = updated_doc['id']
255
+ st.experimental_rerun()
256
+ else:
257
+ st.error(message)
258
+ except json.JSONDecodeError as e:
259
+ st.error(f"Invalid JSON: {str(e)} 🚫")
260
+ else:
261
+ # Allow user to select a document from the sidebar
262
+ document_ids = [doc.get('id', 'Unknown') for doc in documents_to_display]
263
+ selected_document = st.sidebar.selectbox("πŸ“„ Select Document", document_ids, index=document_ids.index(st.session_state.selected_document_id) if st.session_state.selected_document_id in document_ids else 0)
264
+
265
+ if selected_document != st.session_state.selected_document_id:
266
+ st.session_state.selected_document_id = selected_document
267
+ st.experimental_rerun()
268
+
269
+ if st.session_state.selected_document_id:
270
+ st.subheader(f"πŸ“„ Document Details: {st.session_state.selected_document_id}")
271
+ selected_doc = next((doc for doc in documents_to_display if doc.get('id') == st.session_state.selected_document_id), None)
272
+ if selected_doc:
273
+ if selected_view == 'Show as Markdown':
274
+ # πŸ–ŒοΈ Show as Markdown
275
+ content = selected_doc.get('content', '')
276
+ if isinstance(content, dict) or isinstance(content, list):
277
+ content = json.dumps(content, indent=2)
278
+ st.markdown(content)
279
+ elif selected_view == 'Show as Code Editor':
280
+ # πŸ’» Show as Code Editor
281
+ st.code(json.dumps(selected_doc, indent=2), language='python')
282
+ elif selected_view == 'Clone Document':
283
+ # 🧬 Clone Document
284
+ if st.button("πŸ“„ Clone Document"):
285
+ cloned_doc = selected_doc.copy()
286
+ # Generate a unique ID
287
+ cloned_doc['id'] = generate_unique_id()
288
+ success, message = insert_record(container, cloned_doc)
289
+ if success:
290
+ st.success(f"Document cloned with new id: {cloned_doc['id']} πŸŽ‰")
291
+ st.session_state.selected_document_id = cloned_doc['id']
292
+ # Switch to 'Show as Edit and Save' mode
293
+ st.experimental_rerun()
294
+ else:
295
+ st.error(message)
296
+ elif selected_view == 'New Record':
297
+ # πŸ†• New Record
298
+ st.markdown("#### Create a new document:")
299
+ new_id = st.text_input("ID", value=generate_unique_id(), key='new_id')
300
+ new_doc_str = st.text_area("Document Content (in JSON format)", value='{}', height=300)
301
+ if st.button("βž• Create New Document"):
302
+ try:
303
+ new_doc = json.loads(new_doc_str)
304
+ new_doc['id'] = new_id # Use the provided ID
305
+ success, message = insert_record(container, new_doc)
306
+ if success:
307
+ st.success(f"New document created with id: {new_doc['id']} πŸŽ‰")
308
+ st.session_state.selected_document_id = new_doc['id']
309
+ # Switch to 'Show as Edit and Save' mode
310
+ st.experimental_rerun()
311
+ else:
312
+ st.error(message)
313
+ except json.JSONDecodeError as e:
314
+ st.error(f"Invalid JSON: {str(e)} 🚫")
315
+ else:
316
+ st.sidebar.info("No documents found in this container. πŸ“­")
317
+
318
+ # πŸŽ‰ Main content area
319
+ st.subheader(f"πŸ“Š Container: {st.session_state.selected_container}")
320
+ if st.session_state.selected_container:
321
+ if documents_to_display:
322
+ df = pd.DataFrame(documents_to_display)
323
+ st.dataframe(df)
324
+ else:
325
+ st.info("No documents to display. 🧐")
326
+
327
+ # πŸ™ GitHub section
328
+ st.subheader("πŸ™ GitHub Operations")
329
+ github_token = os.environ.get("GITHUB") # Read GitHub token from environment variable
330
+ source_repo = st.text_input("Source GitHub Repository URL", value="https://github.com/AaronCWacker/AIExamples-8-24-Streamlit")
331
+ new_repo_name = st.text_input("New Repository Name (for cloning)", value=f"AIExample-Clone-{datetime.now().strftime('%Y%m%d_%H%M%S')}")
332
+
333
+ col1, col2 = st.columns(2)
334
+ with col1:
335
+ if st.button("πŸ“₯ Clone Repository"):
336
+ if github_token and source_repo:
337
+ try:
338
+ local_path = f"./temp_repo_{datetime.now().strftime('%Y%m%d%H%M%S')}"
339
+ download_github_repo(source_repo, local_path)
340
+ zip_filename = f"{new_repo_name}.zip"
341
+ create_zip_file(local_path, zip_filename[:-4])
342
+ st.markdown(get_base64_download_link(zip_filename, zip_filename), unsafe_allow_html=True)
343
+ st.success("Repository cloned successfully! πŸŽ‰")
344
+ except Exception as e:
345
+ st.error(f"An error occurred: {str(e)} 😒")
346
+ finally:
347
+ if os.path.exists(local_path):
348
+ shutil.rmtree(local_path)
349
+ if os.path.exists(zip_filename):
350
+ os.remove(zip_filename)
351
+ else:
352
+ st.error("Please ensure GitHub token is set in environment variables and source repository URL is provided. πŸ”‘β“")
353
+
354
+ with col2:
355
+ if st.button("πŸ“€ Push to New Repository"):
356
+ if github_token and source_repo:
357
+ try:
358
+ g = Github(github_token)
359
+ new_repo = create_repo(g, new_repo_name)
360
+ local_path = f"./temp_repo_{datetime.now().strftime('%Y%m%d%H%M%S')}"
361
+ download_github_repo(source_repo, local_path)
362
+ push_to_github(local_path, new_repo, github_token)
363
+ st.success(f"Repository pushed successfully to {new_repo.html_url} πŸš€")
364
+ except Exception as e:
365
+ st.error(f"An error occurred: {str(e)} 😒")
366
+ finally:
367
+ if os.path.exists(local_path):
368
+ shutil.rmtree(local_path)
369
+ else:
370
+ st.error("Please ensure GitHub token is set in environment variables and source repository URL is provided. πŸ”‘β“")
371
+
372
+ except exceptions.CosmosHttpResponseError as e:
373
+ st.error(f"Failed to connect to Cosmos DB. HTTP error: {str(e)} 🚨")
374
+ except Exception as e:
375
+ st.error(f"An unexpected error occurred: {str(e)} 😱")
376
+
377
+ # πŸšͺ Logout button
378
+ if st.session_state.logged_in and st.sidebar.button("πŸšͺ Logout"):
379
+ st.session_state.logged_in = False
380
+ st.session_state.selected_records.clear()
381
+ st.session_state.client = None
382
+ st.session_state.selected_database = None
383
+ st.session_state.selected_container = None
384
+ st.session_state.selected_document_id = None
385
+ st.experimental_rerun()
386
+
387
+ if __name__ == "__main__":
388
+ main()