awacke1 commited on
Commit
9dff9b4
Β·
verified Β·
1 Parent(s): ed39ce7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +696 -712
app.py CHANGED
@@ -15,7 +15,7 @@ import traceback
15
  import uuid
16
  import zipfile
17
  from PIL import Image
18
- from azure.cosmos import CosmosClient, exceptions, PartitionKey
19
  from datetime import datetime
20
  from git import Repo
21
  from github import Github
@@ -33,15 +33,15 @@ helpURL = 'https://huggingface.co/awacke1'
33
  bugURL = 'https://huggingface.co/spaces/awacke1/AzureCosmosDBUI/'
34
  icons = 'πŸ™πŸŒŒπŸ’«'
35
  st.set_page_config(
36
- page_title=title,
37
- page_icon=icons,
38
- layout="wide",
39
- initial_sidebar_state="auto",
40
- menu_items={
41
- 'Get Help': helpURL,
42
- 'Report a bug': bugURL,
43
- 'About': title
44
- }
45
  )
46
 
47
  # Cosmos DB & App URLs
@@ -52,760 +52,744 @@ Key = os.environ.get("Key")
52
  LOCAL_APP_URL = "https://huggingface.co/spaces/awacke1/AzureCosmosDBUI"
53
  CosmosDBUrl = 'https://portal.azure.com/#@AaronCWackergmail.onmicrosoft.com/resource/subscriptions/003fba60-5b3f-48f4-ab36-3ed11bc40816/resourceGroups/datasets/providers/Microsoft.DocumentDB/databaseAccounts/acae-afd/dataExplorer'
54
 
55
- # TTL Constants
56
- SEVEN_DAYS_IN_SECONDS = 7 * 24 * 60 * 60 # 604,800 seconds
57
- THIRTY_DAYS_IN_SECONDS = 30 * 24 * 60 * 60 # 2,592,000 seconds
58
-
59
  # %% ───────────── HELPER FUNCTIONS ─────────────
 
60
  def get_download_link(file_path):
61
- with open(file_path, "rb") as file:
62
- contents = file.read()
63
- b64 = base64.b64encode(contents).decode()
64
- file_name = os.path.basename(file_path)
65
- return f'<a href="data:file/txt;base64,{b64}" download="{file_name}">Download {file_name} πŸ“‚</a>'
66
 
 
67
  def generate_unique_id():
68
- timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
69
- unique_uuid = str(uuid.uuid4())
70
- return_value = f"{timestamp}-{unique_uuid}"
71
- st.write('New ID: ' + return_value)
72
- return return_value
73
 
 
74
  def generate_filename(prompt, file_type):
75
- central = pytz.timezone('US/Central')
76
- safe_date_time = datetime.now(central).strftime("%m%d_%H%M")
77
- safe_prompt = re.sub(r'\W+', '', prompt)[:90]
78
- return f"{safe_date_time}{safe_prompt}.{file_type}"
79
 
 
80
  def create_file(filename, prompt, response, should_save=True):
81
- if not should_save:
82
- return
83
- with open(filename, 'w', encoding='utf-8') as file:
84
- file.write(prompt + "\n\n" + response)
85
 
 
86
  def load_file(file_name):
87
- with open(file_name, "r", encoding='utf-8') as file:
88
- content = file.read()
89
- return content
90
 
 
91
  def display_glossary_entity(k):
92
- search_urls = {
93
- "πŸš€": lambda k: f"/?q={k}",
94
- "πŸ“–": lambda k: f"https://en.wikipedia.org/wiki/{quote(k)}",
95
- "πŸ”": lambda k: f"https://www.google.com/search?q={quote(k)}",
96
- "πŸŽ₯": lambda k: f"https://www.youtube.com/results?search_query={quote(k)}",
97
- }
98
- links_md = ' '.join([f"<a href='{url(k)}' target='_blank'>{emoji}</a>" for emoji, url in search_urls.items()])
99
- st.markdown(f"{k} {links_md}", unsafe_allow_html=True)
100
-
 
101
  def create_zip_of_files(files):
102
- zip_name = "all_files.zip"
103
- with zipfile.ZipFile(zip_name, 'w') as zipf:
104
- for file in files:
105
- zipf.write(file)
106
- return zip_name
107
 
 
108
  def get_video_html(video_path, width="100%"):
109
- video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
110
- return f'''
111
- <video width="{width}" controls autoplay loop>
112
- <source src="{video_url}" type="video/mp4">
113
- Your browser does not support video.
114
- </video>
115
- '''
116
-
 
117
  def get_audio_html(audio_path, width="100%"):
118
- audio_url = f"data:audio/mpeg;base64,{base64.b64encode(open(audio_path, 'rb').read()).decode()}"
119
- return f'''
120
- <audio controls style="width:{width}">
121
- <source src="{audio_url}" type="audio/mpeg">
122
- Your browser does not support audio.
123
- </audio>
124
- '''
125
-
 
126
  def preprocess_text(text):
127
- text = text.replace('\r\n', '\\n').replace('\r', '\\n').replace('\n', '\\n')
128
- text = text.replace('"', '\\"')
129
- text = re.sub(r'[\t]', ' ', text)
130
- text = re.sub(r'[^\x00-\x7F]+', '', text)
131
- return text.strip()
132
-
133
- def format_item_age(timestamp):
134
- current_time = int(time.time())
135
- age_seconds = current_time - timestamp
136
- age_days = age_seconds / (24 * 60 * 60)
137
- return f"{age_seconds:,} seconds ({age_days:.2f} days)"
138
 
139
  # %% ───────────── COSMOS DB FUNCTIONS ─────────────
 
140
  def get_databases(client):
141
- return [db['id'] for db in client.list_databases()]
142
 
 
143
  def get_containers(database):
144
- return [container['id'] for container in database.list_containers()]
145
 
 
146
  def get_documents(container, limit=None):
147
- query = "SELECT * FROM c ORDER BY c._ts DESC"
148
- items = list(container.query_items(query=query, enable_cross_partition_query=True, max_item_count=limit))
149
- for item in items:
150
- if '_ts' in item:
151
- item['age_info'] = format_item_age(item['_ts'])
152
- return items
153
 
 
154
  def insert_record(container, record):
155
- try:
156
- container.create_item(body=record)
157
- return True, "Inserted! πŸŽ‰"
158
- except exceptions.CosmosHttpResponseError as e:
159
- return False, f"HTTP error: {str(e)} 🚨"
160
- except Exception as e:
161
- return False, f"Error: {str(e)} 😱"
162
-
 
163
  def update_record(container, updated_record):
164
- try:
165
- container.upsert_item(body=updated_record)
166
- return True, f"Updated {updated_record['id']} πŸ› οΈ"
167
- except exceptions.CosmosHttpResponseError as e:
168
- return False, f"HTTP error: {str(e)} 🚨"
169
- except Exception as e:
170
- return False, f"Error: {traceback.format_exc()} 😱"
171
-
 
172
  def delete_record(container, record):
173
- try:
174
- if "id" not in record:
175
- return False, "Record must contain an 'id' field. πŸ›‘"
176
- doc_id = record["id"]
177
- if "delete_log" not in st.session_state:
178
- st.session_state.delete_log = []
179
- st.session_state.delete_log.append(f"Attempting to delete document: {json.dumps(record, indent=2)}")
180
- partition_key_value = doc_id
181
- st.session_state.delete_log.append(f"Using ID and Partition Key: {partition_key_value}")
182
- container.delete_item(item=doc_id, partition_key=partition_key_value)
183
- success_msg = f"Record {doc_id} successfully deleted from Cosmos DB. πŸ—‘οΈ"
184
- st.session_state.delete_log.append(success_msg)
185
- return True, success_msg
186
- except exceptions.CosmosResourceNotFoundError:
187
- success_msg = f"Record {doc_id} not found in Cosmos DB (already deleted or never existed). πŸ—‘οΈ"
188
- st.session_state.delete_log.append(success_msg)
189
- return True, success_msg
190
- except exceptions.CosmosHttpResponseError as e:
191
- error_msg = f"HTTP error deleting {doc_id}: {str(e)}. 🚨"
192
- st.session_state.delete_log.append(error_msg)
193
- return False, error_msg
194
- except Exception as e:
195
- error_msg = f"Unexpected error deleting {doc_id}: {str(traceback.format_exc())}. 😱"
196
- st.session_state.delete_log.append(error_msg)
197
- return False, error_msg
198
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  def save_to_cosmos_db(container, query, response1, response2):
200
- try:
201
- if container:
202
- timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
203
- unique_uuid = str(uuid.uuid4())
204
- new_id = f"{timestamp}-{unique_uuid}"
205
- record = {
206
- "id": new_id,
207
- "name": new_id,
208
- "query": query,
209
- "response1": response1,
210
- "response2": response2,
211
- "timestamp": datetime.utcnow().isoformat(),
212
- "type": "ai_response",
213
- "version": "1.0"
214
- }
215
- container.create_item(body=record)
216
- st.success(f"Saved: {record['id']}")
217
- st.session_state.documents = get_documents(container)
218
- else:
219
- st.error("Cosmos container not initialized.")
220
- except Exception as e:
221
- st.error(f"Save error: {str(e)}")
222
-
223
- def set_container_ttl(container_client, ttl_seconds):
224
- try:
225
- container_properties = container_client.read()
226
- container_client.replace_container(
227
- container_client.container_path,
228
- partition_key=container_properties['partitionKey'],
229
- default_ttl=ttl_seconds
230
- )
231
- return True, f"TTL set to {ttl_seconds:,} seconds ({ttl_seconds/(24*60*60):.2f} days)"
232
- except Exception as e:
233
- return False, f"Error setting TTL: {str(e)}"
234
-
235
  def archive_current_container(database_name, container_name, client):
236
- try:
237
- base_dir = "./cosmos_archive_current_container"
238
- if os.path.exists(base_dir):
239
- shutil.rmtree(base_dir)
240
- os.makedirs(base_dir)
241
- db_client = client.get_database_client(database_name)
242
- container_client = db_client.get_container_client(container_name)
243
- items = list(container_client.read_all_items())
244
- container_dir = os.path.join(base_dir, container_name)
245
- os.makedirs(container_dir)
246
- for item in items:
247
- item_id = item.get('id', f"unknown_{datetime.now().strftime('%Y%m%d%H%M%S')}")
248
- with open(os.path.join(container_dir, f"{item_id}.json"), 'w') as f:
249
- json.dump(item, f, indent=2)
250
- archive_name = f"{container_name}_archive_{datetime.now().strftime('%Y%m%d%H%M%S')}"
251
- shutil.make_archive(archive_name, 'zip', base_dir)
252
- return get_download_link(f"{archive_name}.zip")
253
- except Exception as e:
254
- return f"Archive error: {str(e)} 😒"
255
 
256
  # %% ───────────── GITHUB FUNCTIONS ─────────────
 
257
  def download_github_repo(url, local_path):
258
- if os.path.exists(local_path):
259
- shutil.rmtree(local_path)
260
- Repo.clone_from(url, local_path)
261
 
 
262
  def create_zip_file(source_dir, output_filename):
263
- shutil.make_archive(output_filename, 'zip', source_dir)
264
 
 
265
  def create_repo(g, repo_name):
266
- user = g.get_user()
267
- return user.create_repo(repo_name)
268
 
 
269
  def push_to_github(local_path, repo, github_token):
270
- repo_url = f"https://{github_token}@github.com/{repo.full_name}.git"
271
- local_repo = Repo(local_path)
272
- if 'origin' in [remote.name for remote in local_repo.remotes]:
273
- origin = local_repo.remote('origin')
274
- origin.set_url(repo_url)
275
- else:
276
- origin = local_repo.create_remote('origin', repo_url)
277
- if not local_repo.heads:
278
- local_repo.git.checkout('-b', 'main')
279
- current_branch = 'main'
280
- else:
281
- current_branch = local_repo.active_branch.name
282
- local_repo.git.add(A=True)
283
- if local_repo.is_dirty():
284
- local_repo.git.commit('-m', 'Initial commit')
285
- origin.push(refspec=f'{current_branch}:{current_branch}')
286
 
287
  # %% ───────────── FILE & MEDIA MANAGEMENT FUNCTIONS ─────────────
 
288
  def display_saved_files_in_sidebar():
289
- all_files = sorted([f for f in glob.glob("*.md") if not f.lower().startswith('readme')], reverse=True)
290
- st.sidebar.markdown("## πŸ“ Files")
291
- for file in all_files:
292
- col1, col2, col3 = st.sidebar.columns([6, 2, 1])
293
- with col1:
294
- st.markdown(f"πŸ“„ {file}")
295
- with col2:
296
- st.sidebar.download_button(
297
- label="⬇️",
298
- data=open(file, 'rb').read(),
299
- file_name=file
300
- )
301
- with col3:
302
- if st.sidebar.button("πŸ—‘", key=f"delete_{file}"):
303
- os.remove(file)
304
- st.rerun()
305
-
 
306
  def display_file_viewer(file_path):
307
- content = load_file(file_path)
308
- if content:
309
- st.markdown("### πŸ“„ File Viewer")
310
- st.markdown(f"**{file_path}**")
311
- file_stats = os.stat(file_path)
312
- st.markdown(f"**Mod:** {datetime.fromtimestamp(file_stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')} | **Size:** {file_stats.st_size} bytes")
313
- st.markdown("---")
314
- st.markdown(content)
315
- st.download_button("⬇️", data=content, file_name=os.path.basename(file_path), mime="text/markdown")
316
-
 
317
  def display_file_editor(file_path):
318
- if 'file_content' not in st.session_state:
319
- st.session_state.file_content = {}
320
- if file_path not in st.session_state.file_content:
321
- content = load_file(file_path)
322
- if content is not None:
323
- st.session_state.file_content[file_path] = content
324
- else:
325
- return
326
- st.markdown("### ✏️ Edit File")
327
- st.markdown(f"**Editing:** {file_path}")
328
- md_tab, code_tab = st.tabs(["Markdown", "Code"])
329
- with md_tab:
330
- st.markdown(st.session_state.file_content[file_path])
331
- with code_tab:
332
- new_content = st.text_area("Edit:", value=st.session_state.file_content[file_path], height=400, key=f"editor_{hash(file_path)}")
333
- col1, col2 = st.columns([1, 5])
334
- with col1:
335
- if st.button("πŸ’Ύ Save"):
336
- if save_file_content(file_path, new_content):
337
- st.session_state.file_content[file_path] = new_content
338
- st.success("Saved! πŸŽ‰")
339
- time.sleep(1)
340
- st.rerun()
341
- with col2:
342
- st.download_button("⬇️", data=new_content, file_name=os.path.basename(file_path), mime="text/markdown")
343
-
 
344
  def save_file_content(file_path, content):
345
- try:
346
- with open(file_path, 'w', encoding='utf-8') as file:
347
- file.write(content)
348
- return True
349
- except Exception as e:
350
- st.error(f"Save error: {str(e)}")
351
- return False
352
-
 
353
  def update_file_management_section():
354
- if 'file_view_mode' not in st.session_state:
355
- st.session_state.file_view_mode = None
356
- if 'current_file' not in st.session_state:
357
- st.session_state.current_file = None
358
- if 'file_content' not in st.session_state:
359
- st.session_state.file_content = {}
360
- all_files = sorted(glob.glob("*.md"), reverse=True)
361
- st.sidebar.title("πŸ“ Files")
362
- if st.sidebar.button("πŸ—‘ Delete All"):
363
- for file in all_files:
364
- os.remove(file)
365
- st.session_state.file_content = {}
366
- st.session_state.current_file = None
367
- st.session_state.file_view_mode = None
368
- st.rerun()
369
- if st.sidebar.button("⬇️ Download All"):
370
- zip_file = create_zip_of_files(all_files)
371
- st.sidebar.markdown(get_download_link(zip_file), unsafe_allow_html=True)
372
- for file in all_files:
373
- col1, col2, col3, col4 = st.sidebar.columns([1, 3, 1, 1])
374
- with col1:
375
- if st.button("🌐", key=f"view_{file}"):
376
- st.session_state.current_file = file
377
- st.session_state.file_view_mode = 'view'
378
- if file not in st.session_state.file_content:
379
- content = load_file(file)
380
- if content is not None:
381
- st.session_state.file_content[file] = content
382
- st.rerun()
383
- with col2:
384
- st.markdown(get_download_link(file), unsafe_allow_html=True)
385
- with col3:
386
- if st.button("πŸ“‚", key=f"edit_{file}"):
387
- st.session_state.current_file = file
388
- st.session_state.file_view_mode = 'edit'
389
- if file not in st.session_state.file_content:
390
- content = load_file(file)
391
- if content is not None:
392
- st.session_state.file_content[file] = content
393
- st.rerun()
394
- with col4:
395
- if st.button("πŸ—‘", key=f"delete_{file}"):
396
- os.remove(file)
397
- if file in st.session_state.file_content:
398
- del st.session_state.file_content[file]
399
- if st.session_state.current_file == file:
400
- st.session_state.current_file = None
401
- st.session_state.file_view_mode = None
402
- st.rerun()
403
- if st.session_state.current_file:
404
- if st.session_state.file_view_mode == 'view':
405
- display_file_viewer(st.session_state.current_file)
406
- elif st.session_state.file_view_mode == 'edit':
407
- display_file_editor(st.session_state.current_file)
408
 
409
  # %% ──────���────── VIDEO & AUDIO UI FUNCTIONS ─────────────
 
410
  def validate_and_preprocess_image(file_data, target_size=(576, 1024)):
411
- try:
412
- st.write("Preprocessing image...")
413
- if isinstance(file_data, bytes):
414
- img = Image.open(io.BytesIO(file_data))
415
- elif hasattr(file_data, 'read'):
416
- if hasattr(file_data, 'seek'):
417
- file_data.seek(0)
418
- img = Image.open(file_data)
419
- elif isinstance(file_data, Image.Image):
420
- img = file_data
421
- else:
422
- raise ValueError(f"Unsupported input: {type(file_data)}")
423
- if img.mode != 'RGB':
424
- img = img.convert('RGB')
425
- aspect_ratio = img.size[0] / img.size[1]
426
- if aspect_ratio > target_size[0] / target_size[1]:
427
- new_width = target_size[0]
428
- new_height = int(new_width / aspect_ratio)
429
- else:
430
- new_height = target_size[1]
431
- new_width = int(new_height * aspect_ratio)
432
- new_width = (new_width // 2) * 2
433
- new_height = (new_height // 2) * 2
434
- resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
435
- final_img = Image.new('RGB', target_size, (255, 255, 255))
436
- paste_x = (target_size[0] - new_width) // 2
437
- paste_y = (target_size[1] - new_height) // 2
438
- final_img.paste(resized_img, (paste_x, paste_y))
439
- return final_img
440
- except Exception as e:
441
- st.error(f"Image error: {str(e)}")
442
- return None
443
-
 
444
  def add_video_generation_ui(container):
445
- st.markdown("### πŸŽ₯ Video Gen")
446
- col1, col2 = st.columns([2, 1])
447
- with col1:
448
- uploaded_file = st.file_uploader("Upload Image πŸ–ΌοΈ", type=['png', 'jpg', 'jpeg'])
449
- with col2:
450
- st.markdown("#### Params")
451
- motion = st.slider("🌊 Motion", 1, 255, 127)
452
- fps = st.slider("🎬 FPS", 1, 30, 6)
453
- with st.expander("Advanced"):
454
- use_custom = st.checkbox("Custom Seed")
455
- seed = st.number_input("Seed", value=int(time.time() * 1000)) if use_custom else None
456
- if uploaded_file is not None:
457
- try:
458
- file_data = uploaded_file.read()
459
- preview1, preview2 = st.columns(2)
460
- with preview1:
461
- st.write("Original")
462
- st.image(Image.open(io.BytesIO(file_data)), use_column_width=True)
463
- with preview2:
464
- proc_img = validate_and_preprocess_image(io.BytesIO(file_data))
465
- if proc_img:
466
- st.write("Processed")
467
- st.image(proc_img, use_column_width=True)
468
- else:
469
- st.error("Preprocess failed")
470
- return
471
- if st.button("πŸŽ₯ Generate"):
472
- with st.spinner("Generating video..."):
473
- with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as temp_file:
474
- proc_img.save(temp_file.name, format='PNG')
475
- try:
476
- client = Client("awacke1/stable-video-diffusion", hf_token=os.environ.get("HUGGINGFACE_TOKEN"))
477
- result = client.predict(
478
- image=temp_file.name,
479
- seed=seed if seed is not None else int(time.time() * 1000),
480
- randomize_seed=seed is None,
481
- motion_bucket_id=motion,
482
- fps_id=fps,
483
- api_name="/video"
484
- )
485
- if result and isinstance(result, tuple) and len(result) >= 1:
486
- video_path = result[0].get('video') if isinstance(result[0], dict) else None
487
- if video_path and os.path.exists(video_path):
488
- video_filename = f"generated_video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
489
- shutil.copy(video_path, video_filename)
490
- st.success(f"Video generated! πŸŽ‰")
491
- st.video(video_filename)
492
- if container:
493
- video_record = {
494
- "id": generate_unique_id(),
495
- "type": "generated_video",
496
- "filename": video_filename,
497
- "seed": seed if seed is not None else "random",
498
- "motion": motion,
499
- "fps": fps,
500
- "timestamp": datetime.now().isoformat()
501
- }
502
- success, message = insert_record(container, video_record)
503
- if success:
504
- st.success("DB record saved!")
505
- else:
506
- st.error(f"DB error: {message}")
507
- else:
508
- st.error("Invalid result format")
509
- else:
510
- st.error("No result returned")
511
- except Exception as e:
512
- st.error(f"Video gen error: {str(e)}")
513
- finally:
514
- try:
515
- os.unlink(temp_file.name)
516
- st.write("Temp file removed")
517
- except Exception as e:
518
- st.warning(f"Cleanup error: {str(e)}")
519
- except Exception as e:
520
- st.error(f"Upload error: {str(e)}")
521
 
522
  # %% ───────────── MAIN FUNCTION ─────────────
 
523
  def main():
524
- st.markdown("### πŸ™ GitCosmos - Cosmos & Git Hub")
525
- if "chat_history" not in st.session_state:
526
- st.session_state.chat_history = []
527
-
528
- if Key:
529
- st.session_state.primary_key = Key
530
- st.session_state.logged_in = True
531
- else:
532
- st.error("Missing Cosmos Key πŸ”‘βŒ")
533
- return
534
-
535
- if st.session_state.logged_in:
536
- try:
537
- if st.session_state.get("client") is None:
538
- st.session_state.client = CosmosClient(ENDPOINT, credential=st.session_state.primary_key)
539
-
540
- st.sidebar.title("πŸ™ Navigator")
541
- databases = get_databases(st.session_state.client)
542
- selected_db = st.sidebar.selectbox("πŸ—ƒοΈ DB", databases)
543
- st.markdown(CosmosDBUrl)
544
-
545
- if selected_db != st.session_state.get("selected_database"):
546
- st.session_state.selected_database = selected_db
547
- st.session_state.selected_container = None
548
- st.session_state.selected_document_id = None
549
- st.session_state.current_index = 0
550
- st.rerun()
551
-
552
- if st.session_state.selected_database:
553
- database = st.session_state.client.get_database_client(st.session_state.selected_database)
554
- containers = get_containers(database)
555
- selected_container = st.sidebar.selectbox("πŸ“ Container", containers)
556
-
557
- if selected_container != st.session_state.get("selected_container"):
558
- st.session_state.selected_container = selected_container
559
- st.session_state.selected_document_id = None
560
- st.session_state.current_index = 0
561
- st.rerun()
562
-
563
- if st.session_state.selected_container:
564
- container = database.get_container_client(st.session_state.selected_container)
565
-
566
- st.sidebar.subheader("⏳ TTL Configuration")
567
- ttl_seconds = st.sidebar.slider(
568
- "Set TTL (seconds)",
569
- min_value=SEVEN_DAYS_IN_SECONDS,
570
- max_value=THIRTY_DAYS_IN_SECONDS,
571
- value=SEVEN_DAYS_IN_SECONDS,
572
- step=24*60*60,
573
- format="%d seconds (%d days)"
574
- )
575
- if st.sidebar.button("⏰ Set Container TTL"):
576
- success, message = set_container_ttl(container, ttl_seconds)
577
- if success:
578
- st.sidebar.success(message)
579
- else:
580
- st.sidebar.error(message)
581
-
582
- if st.sidebar.button("πŸ“¦ Export"):
583
- download_link = archive_current_container(st.session_state.selected_database,
584
- st.session_state.selected_container,
585
- st.session_state.client)
586
- if download_link.startswith('<a'):
587
- st.markdown(download_link, unsafe_allow_html=True)
588
- else:
589
- st.error(download_link)
590
-
591
- documents = get_documents(container)
592
- total_docs = len(documents)
593
- num_docs = st.slider("Docs", 1, 20, 1)
594
- documents_to_display = documents[:num_docs] if total_docs > num_docs else documents
595
- st.sidebar.info(f"Showing {len(documents_to_display)} docs")
596
-
597
- view_options = ['Markdown', 'Code', 'Run AI', 'Clone', 'New']
598
- selected_view = st.sidebar.selectbox("View", view_options, index=1)
599
-
600
- if selected_view == 'Markdown':
601
- st.markdown("#### πŸ“„ Markdown")
602
- if documents:
603
- doc = documents[st.session_state.current_index]
604
- content = json.dumps(doc, indent=2)
605
- st.markdown(f"```json\n{content}\n```")
606
- if '_ts' in doc:
607
- st.markdown(f"**Item Age:** {doc['age_info']}")
608
- col_prev, col_next = st.columns(2)
609
- with col_prev:
610
- if st.button("⬅️") and st.session_state.current_index > 0:
611
- st.session_state.current_index -= 1
612
- st.rerun()
613
- with col_next:
614
- if st.button("➑️") and st.session_state.current_index < total_docs - 1:
615
- st.session_state.current_index += 1
616
- st.rerun()
617
-
618
- elif selected_view == 'Code':
619
- st.markdown("#### πŸ’» Code Editor")
620
- if documents:
621
- doc = documents[st.session_state.current_index]
622
- if '_ts' in doc:
623
- st.markdown(f"**Item Age:** {doc['age_info']}")
624
- doc_str = st.text_area("Edit JSON", value=json.dumps(doc, indent=2),
625
- height=300, key=f'code_{st.session_state.current_index}')
626
- col_prev, col_next = st.columns(2)
627
- with col_prev:
628
- if st.button("⬅️") and st.session_state.current_index > 0:
629
- st.session_state.current_index -= 1
630
- st.rerun()
631
- with col_next:
632
- if st.button("➑️") and st.session_state.current_index < total_docs - 1:
633
- st.session_state.current_index += 1
634
- st.rerun()
635
- col_save, col_delete = st.columns(2)
636
- with col_save:
637
- if st.button("πŸ’Ύ Save", key=f'save_{st.session_state.current_index}'):
638
- try:
639
- updated_doc = json.loads(doc_str)
640
- container.upsert_item(body=updated_doc)
641
- st.success(f"Saved {updated_doc['id']}")
642
- st.rerun()
643
- except Exception as e:
644
- st.error(f"Save err: {str(e)}")
645
- with col_delete:
646
- if st.button("πŸ—‘οΈ Delete", key=f'delete_{st.session_state.current_index}'):
647
- try:
648
- current_doc = json.loads(doc_str)
649
- success, message = delete_record(container, current_doc)
650
- if success:
651
- st.success(message)
652
- st.rerun()
653
- else:
654
- st.error(message)
655
- except Exception as e:
656
- st.error(f"Delete err: {str(e)}")
657
- if "delete_log" in st.session_state and st.session_state.delete_log:
658
- st.subheader("Delete Log")
659
- for log_entry in st.session_state.delete_log[-5:]:
660
- st.write(log_entry)
661
-
662
- elif selected_view == 'Run AI':
663
- st.markdown("#### πŸ€– Run AI (stub)")
664
- st.info("AI functionality not implemented.")
665
-
666
- elif selected_view == 'Clone':
667
- st.markdown("#### πŸ“„ Clone")
668
- if documents:
669
- doc = documents[st.session_state.current_index]
670
- st.markdown(f"Original ID: {doc.get('id', '')}")
671
- if '_ts' in doc:
672
- st.markdown(f"**Original Age:** {doc['age_info']}")
673
- new_id = st.text_input("New ID", value=generate_unique_id(), key='new_clone_id')
674
- new_name = st.text_input("New Name", value=f"Clone_{new_id[:8]}", key='new_clone_name')
675
- new_doc = {'id': new_id, 'name': new_name, **{k: v for k, v in doc.items() if k not in ['id', 'name', '_rid', '_self', '_etag', '_attachments', '_ts']}}
676
- doc_str = st.text_area("Edit JSON", value=json.dumps(new_doc, indent=2), height=300, key='clone_preview')
677
- col1, col2 = st.columns(2)
678
- with col1:
679
- if st.button("πŸ”„ Regenerate"):
680
- new_id = generate_unique_id()
681
- st.session_state.new_clone_id = new_id
682
- st.rerun()
683
- with col2:
684
- if st.button("πŸ’Ύ Save Clone"):
685
- try:
686
- final_doc = json.loads(doc_str)
687
- for field in ['_rid', '_self', '_etag', '_attachments', '_ts']:
688
- final_doc.pop(field, None)
689
- container.create_item(body=final_doc)
690
- st.success(f"Cloned {final_doc['id']}")
691
- st.rerun()
692
- except Exception as e:
693
- st.error(f"Clone err: {str(e)}")
694
- col_prev, col_next = st.columns(2)
695
- with col_prev:
696
- if st.button("⬅️") and st.session_state.current_index > 0:
697
- st.session_state.current_index -= 1
698
- st.rerun()
699
- with col_next:
700
- if st.button("➑️") and st.session_state.current_index < total_docs - 1:
701
- st.session_state.current_index += 1
702
- st.rerun()
703
-
704
- elif selected_view == 'New':
705
- st.markdown("#### βž• New Doc")
706
- if st.button("πŸ€– Auto-Gen"):
707
- auto_doc = {
708
- "id": generate_unique_id(),
709
- "name": f"Auto {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
710
- "content": "Auto-generated record.",
711
- "timestamp": datetime.now().isoformat()
712
- }
713
- success, message = insert_record(container, auto_doc)
714
- if success:
715
- st.success(message)
716
- st.rerun()
717
- else:
718
- st.error(message)
719
- else:
720
- new_id = st.text_input("ID", value=generate_unique_id(), key='new_id')
721
- default_doc = {
722
- "id": new_id,
723
- "name": "New Doc",
724
- "content": "",
725
- "timestamp": datetime.now().isoformat()
726
- }
727
- new_doc_str = st.text_area("JSON", value=json.dumps(default_doc, indent=2), height=300)
728
- if st.button("βž• Create"):
729
- try:
730
- cleaned = preprocess_text(new_doc_str)
731
- new_doc = json.loads(cleaned)
732
- new_doc['id'] = new_id
733
- success, message = insert_record(container, new_doc)
734
- if success:
735
- st.success(f"Created {new_doc['id']}")
736
- st.rerun()
737
- else:
738
- st.error(message)
739
- except Exception as e:
740
- st.error(f"Create err: {str(e)}")
741
- st.subheader(f"πŸ“Š {st.session_state.selected_container}")
742
- if documents_to_display:
743
- df = pd.DataFrame(documents_to_display)
744
- if '_ts' in df.columns:
745
- df['age'] = df['_ts'].apply(format_item_age)
746
- st.dataframe(df)
747
- else:
748
- st.info("No docs.")
749
-
750
- st.subheader("πŸ™ GitHub Ops")
751
- github_token = os.environ.get("GITHUB")
752
- source_repo = st.text_input("Source Repo URL", value="https://github.com/AaronCWacker/AIExamples-8-24-Streamlit")
753
- new_repo_name = st.text_input("New Repo Name", value=f"Clone-{datetime.now().strftime('%Y%m%d_%H%M%S')}")
754
- col_g1, col_g2 = st.columns(2)
755
- with col_g1:
756
- if st.button("πŸ“₯ Clone Repo"):
757
- if github_token and source_repo:
758
- try:
759
- local_path = f"./temp_repo_{datetime.now().strftime('%Y%m%d%H%M%S')}"
760
- download_github_repo(source_repo, local_path)
761
- zip_filename = f"{new_repo_name}.zip"
762
- create_zip_file(local_path, zip_filename[:-4])
763
- st.markdown(get_download_link(zip_filename), unsafe_allow_html=True)
764
- st.success("Cloned! πŸŽ‰")
765
- except Exception as e:
766
- st.error(f"Clone err: {str(e)}")
767
- finally:
768
- if os.path.exists(local_path):
769
- shutil.rmtree(local_path)
770
- if os.path.exists(zip_filename):
771
- os.remove(zip_filename)
772
- else:
773
- st.error("Missing token or URL πŸ”‘β“")
774
- with col_g2:
775
- if st.button("πŸ“€ Push Repo"):
776
- if github_token and source_repo:
777
- try:
778
- g = Github(github_token)
779
- new_repo = create_repo(g, new_repo_name)
780
- local_path = f"./temp_repo_{datetime.now().strftime('%Y%m%d%H%M%S')}"
781
- download_github_repo(source_repo, local_path)
782
- push_to_github(local_path, new_repo, github_token)
783
- st.success(f"Pushed to {new_repo.html_url} πŸš€")
784
- except Exception as e:
785
- st.error(f"Push err: {str(e)}")
786
- finally:
787
- if os.path.exists(local_path):
788
- shutil.rmtree(local_path)
789
- else:
790
- st.error("Missing token or URL πŸ”‘β“")
791
-
792
- update_file_management_section()
793
-
794
- except exceptions.CosmosHttpResponseError as e:
795
- st.error(f"Cosmos error: {str(e)} 🚨")
796
- except Exception as e:
797
- st.error(f"Error: {str(e)} 😱")
798
-
799
- if st.session_state.logged_in and st.sidebar.button("πŸšͺ Logout"):
800
- st.markdown("#### πŸšͺ Logout")
801
- st.session_state.logged_in = False
802
- st.session_state.selected_records = []
803
- st.session_state.client = None
804
- st.session_state.selected_database = None
805
- st.session_state.selected_container = None
806
- st.session_state.selected_document_id = None
807
- st.session_state.current_index = 0
808
- st.rerun()
809
 
810
  if __name__ == "__main__":
811
- main()
 
15
  import uuid
16
  import zipfile
17
  from PIL import Image
18
+ from azure.cosmos import CosmosClient, exceptions
19
  from datetime import datetime
20
  from git import Repo
21
  from github import Github
 
33
  bugURL = 'https://huggingface.co/spaces/awacke1/AzureCosmosDBUI/'
34
  icons = 'πŸ™πŸŒŒπŸ’«'
35
  st.set_page_config(
36
+ page_title=title,
37
+ page_icon=icons,
38
+ layout="wide",
39
+ initial_sidebar_state="auto",
40
+ menu_items={
41
+ 'Get Help': helpURL,
42
+ 'Report a bug': bugURL,
43
+ 'About': title
44
+ }
45
  )
46
 
47
  # Cosmos DB & App URLs
 
52
  LOCAL_APP_URL = "https://huggingface.co/spaces/awacke1/AzureCosmosDBUI"
53
  CosmosDBUrl = 'https://portal.azure.com/#@AaronCWackergmail.onmicrosoft.com/resource/subscriptions/003fba60-5b3f-48f4-ab36-3ed11bc40816/resourceGroups/datasets/providers/Microsoft.DocumentDB/databaseAccounts/acae-afd/dataExplorer'
54
 
 
 
 
 
55
  # %% ───────────── HELPER FUNCTIONS ─────────────
56
+ # πŸ“Ž Get download link from file
57
  def get_download_link(file_path):
58
+ with open(file_path, "rb") as file:
59
+ contents = file.read()
60
+ b64 = base64.b64encode(contents).decode()
61
+ file_name = os.path.basename(file_path)
62
+ return f'<a href="data:file/txt;base64,{b64}" download="{file_name}">Download {file_name} πŸ“‚</a>'
63
 
64
+ # πŸ†” Generate a unique ID
65
  def generate_unique_id():
66
+ timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
67
+ unique_uuid = str(uuid.uuid4())
68
+ return_value = f"{timestamp}-{unique_uuid}"
69
+ st.write('New ID: ' + return_value)
70
+ return return_value
71
 
72
+ # πŸ“„ Generate a filename from a prompt
73
  def generate_filename(prompt, file_type):
74
+ central = pytz.timezone('US/Central')
75
+ safe_date_time = datetime.now(central).strftime("%m%d_%H%M")
76
+ safe_prompt = re.sub(r'\W+', '', prompt)[:90]
77
+ return f"{safe_date_time}{safe_prompt}.{file_type}"
78
 
79
+ # πŸ’Ύ Create and save a file with prompt & response
80
  def create_file(filename, prompt, response, should_save=True):
81
+ if not should_save:
82
+ return
83
+ with open(filename, 'w', encoding='utf-8') as file:
84
+ file.write(prompt + "\n\n" + response)
85
 
86
+ # πŸ“– Load file content from disk
87
  def load_file(file_name):
88
+ with open(file_name, "r", encoding='utf-8') as file:
89
+ content = file.read()
90
+ return content
91
 
92
+ # πŸ” Display glossary entity with quick links
93
  def display_glossary_entity(k):
94
+ search_urls = {
95
+ "πŸš€": lambda k: f"/?q={k}",
96
+ "πŸ“–": lambda k: f"https://en.wikipedia.org/wiki/{quote(k)}",
97
+ "πŸ”": lambda k: f"https://www.google.com/search?q={quote(k)}",
98
+ "πŸŽ₯": lambda k: f"https://www.youtube.com/results?search_query={quote(k)}",
99
+ }
100
+ links_md = ' '.join([f"<a href='{url(k)}' target='_blank'>{emoji}</a>" for emoji, url in search_urls.items()])
101
+ st.markdown(f"{k} {links_md}", unsafe_allow_html=True)
102
+
103
+ # πŸ—œοΈ Create zip of multiple files
104
  def create_zip_of_files(files):
105
+ zip_name = "all_files.zip"
106
+ with zipfile.ZipFile(zip_name, 'w') as zipf:
107
+ for file in files:
108
+ zipf.write(file)
109
+ return zip_name
110
 
111
+ # 🎬 Get HTML for video playback
112
  def get_video_html(video_path, width="100%"):
113
+ video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
114
+ return f'''
115
+ <video width="{width}" controls autoplay loop>
116
+ <source src="{video_url}" type="video/mp4">
117
+ Your browser does not support video.
118
+ </video>
119
+ '''
120
+
121
+ # 🎡 Get HTML for audio playback
122
  def get_audio_html(audio_path, width="100%"):
123
+ audio_url = f"data:audio/mpeg;base64,{base64.b64encode(open(audio_path, 'rb').read()).decode()}"
124
+ return f'''
125
+ <audio controls style="width:{width}">
126
+ <source src="{audio_url}" type="audio/mpeg">
127
+ Your browser does not support audio.
128
+ </audio>
129
+ '''
130
+
131
+ # πŸ“ Preprocess text for JSON safety
132
  def preprocess_text(text):
133
+ text = text.replace('\r\n', '\\n').replace('\r', '\\n').replace('\n', '\\n')
134
+ text = text.replace('"', '\\"')
135
+ text = re.sub(r'[\t]', ' ', text)
136
+ text = re.sub(r'[^\x00-\x7F]+', '', text)
137
+ return text.strip()
 
 
 
 
 
 
138
 
139
  # %% ───────────── COSMOS DB FUNCTIONS ─────────────
140
+ # πŸ“š List all databases in Cosmos
141
  def get_databases(client):
142
+ return [db['id'] for db in client.list_databases()]
143
 
144
+ # πŸ“¦ List all containers in a database
145
  def get_containers(database):
146
+ return [container['id'] for container in database.list_containers()]
147
 
148
+ # πŸ“„ Query documents from a container (most recent first)
149
  def get_documents(container, limit=None):
150
+ query = "SELECT * FROM c ORDER BY c._ts DESC"
151
+ items = list(container.query_items(query=query, enable_cross_partition_query=True, max_item_count=limit))
152
+ return items
 
 
 
153
 
154
+ # πŸ“₯ Insert a new record into Cosmos
155
  def insert_record(container, record):
156
+ try:
157
+ container.create_item(body=record)
158
+ return True, "Inserted! πŸŽ‰"
159
+ except exceptions.CosmosHttpResponseError as e:
160
+ return False, f"HTTP error: {str(e)} 🚨"
161
+ except Exception as e:
162
+ return False, f"Error: {str(e)} 😱"
163
+
164
+ # πŸ”„ Update an existing Cosmos record
165
  def update_record(container, updated_record):
166
+ try:
167
+ container.upsert_item(body=updated_record)
168
+ return True, f"Updated {updated_record['id']} πŸ› οΈ"
169
+ except exceptions.CosmosHttpResponseError as e:
170
+ return False, f"HTTP error: {str(e)} 🚨"
171
+ except Exception as e:
172
+ return False, f"Error: {traceback.format_exc()} 😱"
173
+
174
+ # πŸ—‘οΈ Delete a record from Cosmos DB using /id as partition key
175
  def delete_record(container, record):
176
+ """
177
+ Delete a record from Cosmos DB using its 'id' field as both the item ID and partition key.
178
+
179
+ Args:
180
+ container: Cosmos DB container client
181
+ record: Dictionary containing at least 'id'
182
+
183
+ Returns:
184
+ tuple: (success: bool, message: str)
185
+ """
186
+ try:
187
+ # Ensure record has an ID
188
+ if "id" not in record:
189
+ return False, "Record must contain an 'id' field. πŸ›‘"
190
+
191
+ doc_id = record["id"]
192
+
193
+ # Log the document being deleted (persistent in session state)
194
+ if "delete_log" not in st.session_state:
195
+ st.session_state.delete_log = []
196
+ st.session_state.delete_log.append(f"Attempting to delete document: {json.dumps(record, indent=2)}")
197
+
198
+ # Use 'id' as the partition key since container is partitioned on /id
199
+ partition_key_value = doc_id
200
+ st.session_state.delete_log.append(f"Using ID and Partition Key: {partition_key_value}")
201
+
202
+ # Perform the deletion
203
+ container.delete_item(item=doc_id, partition_key=partition_key_value)
204
+ success_msg = f"Record {doc_id} successfully deleted from Cosmos DB. πŸ—‘οΈ"
205
+ st.session_state.delete_log.append(success_msg)
206
+ return True, success_msg
207
+
208
+ except exceptions.CosmosResourceNotFoundError:
209
+ success_msg = f"Record {doc_id} not found in Cosmos DB (already deleted or never existed). πŸ—‘οΈ"
210
+ st.session_state.delete_log.append(success_msg)
211
+ return True, success_msg # Treat as success since the goal is removal
212
+ except exceptions.CosmosHttpResponseError as e:
213
+ error_msg = f"HTTP error deleting {doc_id}: {str(e)}. 🚨"
214
+ st.session_state.delete_log.append(error_msg)
215
+ return False, error_msg
216
+ except Exception as e:
217
+ error_msg = f"Unexpected error deleting {doc_id}: {str(traceback.format_exc())}. 😱"
218
+ st.session_state.delete_log.append(error_msg)
219
+ return False, error_msg
220
+
221
+ # πŸ’Ύ Save a new document to Cosmos DB with extra fields
222
  def save_to_cosmos_db(container, query, response1, response2):
223
+ try:
224
+ if container:
225
+ timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
226
+ unique_uuid = str(uuid.uuid4())
227
+ new_id = f"{timestamp}-{unique_uuid}"
228
+ record = {
229
+ "id": new_id,
230
+ "name": new_id,
231
+ "query": query,
232
+ "response1": response1,
233
+ "response2": response2,
234
+ "timestamp": datetime.utcnow().isoformat(),
235
+ "type": "ai_response",
236
+ "version": "1.0"
237
+ }
238
+ container.create_item(body=record)
239
+ st.success(f"Saved: {record['id']}")
240
+ st.session_state.documents = get_documents(container)
241
+ else:
242
+ st.error("Cosmos container not initialized.")
243
+ except Exception as e:
244
+ st.error(f"Save error: {str(e)}")
245
+
246
+ # πŸ—„οΈ Archive all documents in a container and provide a download link
 
 
 
 
 
 
 
 
 
 
 
247
  def archive_current_container(database_name, container_name, client):
248
+ try:
249
+ base_dir = "./cosmos_archive_current_container"
250
+ if os.path.exists(base_dir):
251
+ shutil.rmtree(base_dir)
252
+ os.makedirs(base_dir)
253
+ db_client = client.get_database_client(database_name)
254
+ container_client = db_client.get_container_client(container_name)
255
+ items = list(container_client.read_all_items())
256
+ container_dir = os.path.join(base_dir, container_name)
257
+ os.makedirs(container_dir)
258
+ for item in items:
259
+ item_id = item.get('id', f"unknown_{datetime.now().strftime('%Y%m%d%H%M%S')}")
260
+ with open(os.path.join(container_dir, f"{item_id}.json"), 'w') as f:
261
+ json.dump(item, f, indent=2)
262
+ archive_name = f"{container_name}_archive_{datetime.now().strftime('%Y%m%d%H%M%S')}"
263
+ shutil.make_archive(archive_name, 'zip', base_dir)
264
+ return get_download_link(f"{archive_name}.zip")
265
+ except Exception as e:
266
+ return f"Archive error: {str(e)} 😒"
267
 
268
  # %% ───────────── GITHUB FUNCTIONS ─────────────
269
+ # πŸ“₯ Clone a GitHub repository locally
270
  def download_github_repo(url, local_path):
271
+ if os.path.exists(local_path):
272
+ shutil.rmtree(local_path)
273
+ Repo.clone_from(url, local_path)
274
 
275
+ # πŸ—œοΈ Zip a directory
276
  def create_zip_file(source_dir, output_filename):
277
+ shutil.make_archive(output_filename, 'zip', source_dir)
278
 
279
+ # πŸ—οΈ Create a new GitHub repo via API
280
  def create_repo(g, repo_name):
281
+ user = g.get_user()
282
+ return user.create_repo(repo_name)
283
 
284
+ # πŸš€ Push local repo changes to GitHub
285
  def push_to_github(local_path, repo, github_token):
286
+ repo_url = f"https://{github_token}@github.com/{repo.full_name}.git"
287
+ local_repo = Repo(local_path)
288
+ if 'origin' in [remote.name for remote in local_repo.remotes]:
289
+ origin = local_repo.remote('origin')
290
+ origin.set_url(repo_url)
291
+ else:
292
+ origin = local_repo.create_remote('origin', repo_url)
293
+ if not local_repo.heads:
294
+ local_repo.git.checkout('-b', 'main')
295
+ current_branch = 'main'
296
+ else:
297
+ current_branch = local_repo.active_branch.name
298
+ local_repo.git.add(A=True)
299
+ if local_repo.is_dirty():
300
+ local_repo.git.commit('-m', 'Initial commit')
301
+ origin.push(refspec=f'{current_branch}:{current_branch}')
302
 
303
  # %% ───────────── FILE & MEDIA MANAGEMENT FUNCTIONS ─────────────
304
+ # πŸ“‚ List saved Markdown files in sidebar with actions
305
  def display_saved_files_in_sidebar():
306
+ all_files = sorted([f for f in glob.glob("*.md") if not f.lower().startswith('readme')], reverse=True)
307
+ st.sidebar.markdown("## πŸ“ Files")
308
+ for file in all_files:
309
+ col1, col2, col3 = st.sidebar.columns([6, 2, 1])
310
+ with col1:
311
+ st.markdown(f"πŸ“„ {file}")
312
+ with col2:
313
+ st.sidebar.download_button(
314
+ label="⬇️",
315
+ data=open(file, 'rb').read(),
316
+ file_name=file
317
+ )
318
+ with col3:
319
+ if st.sidebar.button("πŸ—‘", key=f"delete_{file}"):
320
+ os.remove(file)
321
+ st.rerun()
322
+
323
+ # πŸ‘€ Display file viewer in main area
324
  def display_file_viewer(file_path):
325
+ content = load_file(file_path)
326
+ if content:
327
+ st.markdown("### πŸ“„ File Viewer")
328
+ st.markdown(f"**{file_path}**")
329
+ file_stats = os.stat(file_path)
330
+ st.markdown(f"**Mod:** {datetime.fromtimestamp(file_stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')} | **Size:** {file_stats.st_size} bytes")
331
+ st.markdown("---")
332
+ st.markdown(content)
333
+ st.download_button("⬇️", data=content, file_name=os.path.basename(file_path), mime="text/markdown")
334
+
335
+ # ✏️ Display file editor (Markdown & Code)
336
  def display_file_editor(file_path):
337
+ if 'file_content' not in st.session_state:
338
+ st.session_state.file_content = {}
339
+ if file_path not in st.session_state.file_content:
340
+ content = load_file(file_path)
341
+ if content is not None:
342
+ st.session_state.file_content[file_path] = content
343
+ else:
344
+ return
345
+ st.markdown("### ✏️ Edit File")
346
+ st.markdown(f"**Editing:** {file_path}")
347
+ md_tab, code_tab = st.tabs(["Markdown", "Code"])
348
+ with md_tab:
349
+ st.markdown(st.session_state.file_content[file_path])
350
+ with code_tab:
351
+ new_content = st.text_area("Edit:", value=st.session_state.file_content[file_path], height=400, key=f"editor_{hash(file_path)}")
352
+ col1, col2 = st.columns([1, 5])
353
+ with col1:
354
+ if st.button("πŸ’Ύ Save"):
355
+ if save_file_content(file_path, new_content):
356
+ st.session_state.file_content[file_path] = new_content
357
+ st.success("Saved! πŸŽ‰")
358
+ time.sleep(1)
359
+ st.rerun()
360
+ with col2:
361
+ st.download_button("⬇️", data=new_content, file_name=os.path.basename(file_path), mime="text/markdown")
362
+
363
+ # πŸ’Ύ Save content to a file (with error handling)
364
  def save_file_content(file_path, content):
365
+ try:
366
+ with open(file_path, 'w', encoding='utf-8') as file:
367
+ file.write(content)
368
+ return True
369
+ except Exception as e:
370
+ st.error(f"Save error: {str(e)}")
371
+ return False
372
+
373
+ # πŸ—‚οΈ Update file management UI section (view, edit, delete)
374
  def update_file_management_section():
375
+ if 'file_view_mode' not in st.session_state:
376
+ st.session_state.file_view_mode = None
377
+ if 'current_file' not in st.session_state:
378
+ st.session_state.current_file = None
379
+ if 'file_content' not in st.session_state:
380
+ st.session_state.file_content = {}
381
+ all_files = sorted(glob.glob("*.md"), reverse=True)
382
+ st.sidebar.title("πŸ“ Files")
383
+ if st.sidebar.button("πŸ—‘ Delete All"):
384
+ for file in all_files:
385
+ os.remove(file)
386
+ st.session_state.file_content = {}
387
+ st.session_state.current_file = None
388
+ st.session_state.file_view_mode = None
389
+ st.rerun()
390
+ if st.sidebar.button("⬇️ Download All"):
391
+ zip_file = create_zip_of_files(all_files)
392
+ st.sidebar.markdown(get_download_link(zip_file), unsafe_allow_html=True)
393
+ for file in all_files:
394
+ col1, col2, col3, col4 = st.sidebar.columns([1, 3, 1, 1])
395
+ with col1:
396
+ if st.button("🌐", key=f"view_{file}"):
397
+ st.session_state.current_file = file
398
+ st.session_state.file_view_mode = 'view'
399
+ if file not in st.session_state.file_content:
400
+ content = load_file(file)
401
+ if content is not None:
402
+ st.session_state.file_content[file] = content
403
+ st.rerun()
404
+ with col2:
405
+ st.markdown(get_download_link(file), unsafe_allow_html=True)
406
+ with col3:
407
+ if st.button("πŸ“‚", key=f"edit_{file}"):
408
+ st.session_state.current_file = file
409
+ st.session_state.file_view_mode = 'edit'
410
+ if file not in st.session_state.file_content:
411
+ content = load_file(file)
412
+ if content is not None:
413
+ st.session_state.file_content[file] = content
414
+ st.rerun()
415
+ with col4:
416
+ if st.button("πŸ—‘", key=f"delete_{file}"):
417
+ os.remove(file)
418
+ if file in st.session_state.file_content:
419
+ del st.session_state.file_content[file]
420
+ if st.session_state.current_file == file:
421
+ st.session_state.current_file = None
422
+ st.session_state.file_view_mode = None
423
+ st.rerun()
424
+ if st.session_state.current_file:
425
+ if st.session_state.file_view_mode == 'view':
426
+ display_file_viewer(st.session_state.current_file)
427
+ elif st.session_state.file_view_mode == 'edit':
428
+ display_file_editor(st.session_state.current_file)
429
 
430
  # %% ──────���────── VIDEO & AUDIO UI FUNCTIONS ─────────────
431
+ # πŸ–ΌοΈ Validate and preprocess an image for video generation
432
  def validate_and_preprocess_image(file_data, target_size=(576, 1024)):
433
+ try:
434
+ st.write("Preprocessing image...")
435
+ if isinstance(file_data, bytes):
436
+ img = Image.open(io.BytesIO(file_data))
437
+ elif hasattr(file_data, 'read'):
438
+ if hasattr(file_data, 'seek'):
439
+ file_data.seek(0)
440
+ img = Image.open(file_data)
441
+ elif isinstance(file_data, Image.Image):
442
+ img = file_data
443
+ else:
444
+ raise ValueError(f"Unsupported input: {type(file_data)}")
445
+ if img.mode != 'RGB':
446
+ img = img.convert('RGB')
447
+ aspect_ratio = img.size[0] / img.size[1]
448
+ if aspect_ratio > target_size[0] / target_size[1]:
449
+ new_width = target_size[0]
450
+ new_height = int(new_width / aspect_ratio)
451
+ else:
452
+ new_height = target_size[1]
453
+ new_width = int(new_height * aspect_ratio)
454
+ new_width = (new_width // 2) * 2
455
+ new_height = (new_height // 2) * 2
456
+ resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
457
+ final_img = Image.new('RGB', target_size, (255, 255, 255))
458
+ paste_x = (target_size[0] - new_width) // 2
459
+ paste_y = (target_size[1] - new_height) // 2
460
+ final_img.paste(resized_img, (paste_x, paste_y))
461
+ return final_img
462
+ except Exception as e:
463
+ st.error(f"Image error: {str(e)}")
464
+ return None
465
+
466
+ # ▢️ Add video generation UI with Gradio client
467
  def add_video_generation_ui(container):
468
+ st.markdown("### πŸŽ₯ Video Gen")
469
+ col1, col2 = st.columns([2, 1])
470
+ with col1:
471
+ uploaded_file = st.file_uploader("Upload Image πŸ–ΌοΈ", type=['png', 'jpg', 'jpeg'])
472
+ with col2:
473
+ st.markdown("#### Params")
474
+ motion = st.slider("🌊 Motion", 1, 255, 127)
475
+ fps = st.slider("🎬 FPS", 1, 30, 6)
476
+ with st.expander("Advanced"):
477
+ use_custom = st.checkbox("Custom Seed")
478
+ seed = st.number_input("Seed", value=int(time.time() * 1000)) if use_custom else None
479
+ if uploaded_file is not None:
480
+ try:
481
+ file_data = uploaded_file.read()
482
+ preview1, preview2 = st.columns(2)
483
+ with preview1:
484
+ st.write("Original")
485
+ st.image(Image.open(io.BytesIO(file_data)), use_column_width=True)
486
+ with preview2:
487
+ proc_img = validate_and_preprocess_image(io.BytesIO(file_data))
488
+ if proc_img:
489
+ st.write("Processed")
490
+ st.image(proc_img, use_column_width=True)
491
+ else:
492
+ st.error("Preprocess failed")
493
+ return
494
+ if st.button("πŸŽ₯ Generate"):
495
+ with st.spinner("Generating video..."):
496
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as temp_file:
497
+ proc_img.save(temp_file.name, format='PNG')
498
+ try:
499
+ client = Client("awacke1/stable-video-diffusion", hf_token=os.environ.get("HUGGINGFACE_TOKEN"))
500
+ result = client.predict(
501
+ image=temp_file.name,
502
+ seed=seed if seed is not None else int(time.time() * 1000),
503
+ randomize_seed=seed is None,
504
+ motion_bucket_id=motion,
505
+ fps_id=fps,
506
+ api_name="/video"
507
+ )
508
+ if result and isinstance(result, tuple) and len(result) >= 1:
509
+ video_path = result[0].get('video') if isinstance(result[0], dict) else None
510
+ if video_path and os.path.exists(video_path):
511
+ video_filename = f"generated_video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
512
+ shutil.copy(video_path, video_filename)
513
+ st.success(f"Video generated! πŸŽ‰")
514
+ st.video(video_filename)
515
+ if container:
516
+ video_record = {
517
+ "id": generate_unique_id(),
518
+ "type": "generated_video",
519
+ "filename": video_filename,
520
+ "seed": seed if seed is not None else "random",
521
+ "motion": motion,
522
+ "fps": fps,
523
+ "timestamp": datetime.now().isoformat()
524
+ }
525
+ success, message = insert_record(container, video_record)
526
+ if success:
527
+ st.success("DB record saved!")
528
+ else:
529
+ st.error(f"DB error: {message}")
530
+ else:
531
+ st.error("Invalid result format")
532
+ else:
533
+ st.error("No result returned")
534
+ except Exception as e:
535
+ st.error(f"Video gen error: {str(e)}")
536
+ finally:
537
+ try:
538
+ os.unlink(temp_file.name)
539
+ st.write("Temp file removed")
540
+ except Exception as e:
541
+ st.warning(f"Cleanup error: {str(e)}")
542
+ except Exception as e:
543
+ st.error(f"Upload error: {str(e)}")
544
 
545
  # %% ───────────── MAIN FUNCTION ─────────────
546
+ # πŸš€ Main app entry point
547
  def main():
548
+ st.markdown("### πŸ™ GitCosmos - Cosmos & Git Hub")
549
+ if "chat_history" not in st.session_state:
550
+ st.session_state.chat_history = []
551
+ # Auth & Cosmos client initialization
552
+ if Key:
553
+ st.session_state.primary_key = Key
554
+ st.session_state.logged_in = True
555
+ else:
556
+ st.error("Missing Cosmos Key πŸ”‘βŒ")
557
+ return
558
+ if st.session_state.logged_in:
559
+ try:
560
+ if st.session_state.get("client") is None:
561
+ st.session_state.client = CosmosClient(ENDPOINT, credential=st.session_state.primary_key)
562
+ st.sidebar.title("πŸ™ Navigator")
563
+ databases = get_databases(st.session_state.client)
564
+ selected_db = st.sidebar.selectbox("πŸ—ƒοΈ DB", databases)
565
+ st.markdown(CosmosDBUrl)
566
+ if selected_db != st.session_state.get("selected_database"):
567
+ st.session_state.selected_database = selected_db
568
+ st.session_state.selected_container = None
569
+ st.session_state.selected_document_id = None
570
+ st.session_state.current_index = 0
571
+ st.rerun()
572
+ if st.session_state.selected_database:
573
+ database = st.session_state.client.get_database_client(st.session_state.selected_database)
574
+ containers = get_containers(database)
575
+ selected_container = st.sidebar.selectbox("πŸ“ Container", containers)
576
+ if selected_container != st.session_state.get("selected_container"):
577
+ st.session_state.selected_container = selected_container
578
+ st.session_state.selected_document_id = None
579
+ st.session_state.current_index = 0
580
+ st.rerun()
581
+ if st.session_state.selected_container:
582
+ container = database.get_container_client(st.session_state.selected_container)
583
+ if st.sidebar.button("πŸ“¦ Export"):
584
+ download_link = archive_current_container(st.session_state.selected_database, st.session_state.selected_container, st.session_state.client)
585
+ if download_link.startswith('<a'):
586
+ st.markdown(download_link, unsafe_allow_html=True)
587
+ else:
588
+ st.error(download_link)
589
+ documents = get_documents(container)
590
+ total_docs = len(documents)
591
+ num_docs = st.slider("Docs", 1, 20, 1)
592
+ documents_to_display = documents[:num_docs] if total_docs > num_docs else documents
593
+ st.sidebar.info(f"Showing {len(documents_to_display)} docs")
594
+ # --- Document Viewer / Editor ---
595
+ view_options = ['Markdown', 'Code', 'Run AI', 'Clone', 'New']
596
+ selected_view = st.sidebar.selectbox("View", view_options, index=1)
597
+ if selected_view == 'Markdown':
598
+ st.markdown("#### πŸ“„ Markdown")
599
+ if documents:
600
+ doc = documents[st.session_state.current_index]
601
+ content = json.dumps(doc, indent=2)
602
+ st.markdown(f"```json\n{content}\n```")
603
+ col_prev, col_next = st.columns(2)
604
+ with col_prev:
605
+ if st.button("⬅️") and st.session_state.current_index > 0:
606
+ st.session_state.current_index -= 1
607
+ st.rerun()
608
+ with col_next:
609
+ if st.button("➑️") and st.session_state.current_index < total_docs - 1:
610
+ st.session_state.current_index += 1
611
+ st.rerun()
612
+ elif selected_view == 'Code':
613
+ st.markdown("#### πŸ’» Code Editor")
614
+ if documents:
615
+ doc = documents[st.session_state.current_index]
616
+ doc_str = st.text_area("Edit JSON", value=json.dumps(doc, indent=2), height=300, key=f'code_{st.session_state.current_index}')
617
+ col_prev, col_next = st.columns(2)
618
+ with col_prev:
619
+ if st.button("⬅️") and st.session_state.current_index > 0:
620
+ st.session_state.current_index -= 1
621
+ st.rerun()
622
+ with col_next:
623
+ if st.button("➑️") and st.session_state.current_index < total_docs - 1:
624
+ st.session_state.current_index += 1
625
+ st.rerun()
626
+ col_save, col_delete = st.columns(2)
627
+ with col_save:
628
+ if st.button("πŸ’Ύ Save", key=f'save_{st.session_state.current_index}'):
629
+ try:
630
+ updated_doc = json.loads(doc_str)
631
+ container.upsert_item(body=updated_doc)
632
+ st.success(f"Saved {updated_doc['id']}")
633
+ st.rerun()
634
+ except Exception as e:
635
+ st.error(f"Save err: {str(e)}")
636
+ with col_delete:
637
+ if st.button("πŸ—‘οΈ Delete", key=f'delete_{st.session_state.current_index}'):
638
+ try:
639
+ current_doc = json.loads(doc_str)
640
+ success, message = delete_record(container, current_doc)
641
+ if success:
642
+ st.success(message)
643
+ st.rerun()
644
+ else:
645
+ st.error(message)
646
+ except Exception as e:
647
+ st.error(f"Delete err: {str(e)}")
648
+
649
+ # Display delete log persistently
650
+ if "delete_log" in st.session_state and st.session_state.delete_log:
651
+ st.subheader("Delete Log")
652
+ for log_entry in st.session_state.delete_log[-5:]: # Show last 5 entries
653
+ st.write(log_entry)
654
+ elif selected_view == 'Run AI':
655
+ st.markdown("#### πŸ€– Run AI (stub)")
656
+ st.info("AI functionality not implemented.")
657
+ elif selected_view == 'Clone':
658
+ st.markdown("#### πŸ“„ Clone")
659
+ if documents:
660
+ doc = documents[st.session_state.current_index]
661
+ st.markdown(f"Original ID: {doc.get('id', '')}")
662
+ new_id = st.text_input("New ID", value=generate_unique_id(), key='new_clone_id')
663
+ new_name = st.text_input("New Name", value=f"Clone_{new_id[:8]}", key='new_clone_name')
664
+ new_doc = {'id': new_id, 'name': new_name, **{k: v for k, v in doc.items() if k not in ['id', 'name', '_rid', '_self', '_etag', '_attachments', '_ts']}}
665
+ doc_str = st.text_area("Edit JSON", value=json.dumps(new_doc, indent=2), height=300, key='clone_preview')
666
+ col1, col2 = st.columns(2)
667
+ with col1:
668
+ if st.button("πŸ”„ Regenerate"):
669
+ new_id = generate_unique_id()
670
+ st.session_state.new_clone_id = new_id
671
+ st.rerun()
672
+ with col2:
673
+ if st.button("πŸ’Ύ Save Clone"):
674
+ try:
675
+ final_doc = json.loads(doc_str)
676
+ for field in ['_rid', '_self', '_etag', '_attachments', '_ts']:
677
+ final_doc.pop(field, None)
678
+ container.create_item(body=final_doc)
679
+ st.success(f"Cloned {final_doc['id']}")
680
+ st.rerun()
681
+ except Exception as e:
682
+ st.error(f"Clone err: {str(e)}")
683
+ col_prev, col_next = st.columns(2)
684
+ with col_prev:
685
+ if st.button("⬅️") and st.session_state.current_index > 0:
686
+ st.session_state.current_index -= 1
687
+ st.rerun()
688
+ with col_next:
689
+ if st.button("➑️") and st.session_state.current_index < total_docs - 1:
690
+ st.session_state.current_index += 1
691
+ st.rerun()
692
+ elif selected_view == 'New':
693
+ st.markdown("#### βž• New Doc")
694
+ if st.button("πŸ€– Auto-Gen"):
695
+ auto_doc = {
696
+ "id": generate_unique_id(),
697
+ "name": f"Auto {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
698
+ "content": "Auto-generated record.",
699
+ "timestamp": datetime.now().isoformat()
700
+ }
701
+ success, message = insert_record(container, auto_doc)
702
+ if success:
703
+ st.success(message)
704
+ st.rerun()
705
+ else:
706
+ st.error(message)
707
+ else:
708
+ new_id = st.text_input("ID", value=generate_unique_id(), key='new_id')
709
+ default_doc = {
710
+ "id": new_id,
711
+ "name": "New Doc",
712
+ "content": "",
713
+ "timestamp": datetime.now().isoformat()
714
+ }
715
+ new_doc_str = st.text_area("JSON", value=json.dumps(default_doc, indent=2), height=300)
716
+ if st.button("βž• Create"):
717
+ try:
718
+ cleaned = preprocess_text(new_doc_str)
719
+ new_doc = json.loads(cleaned)
720
+ new_doc['id'] = new_id
721
+ success, message = insert_record(container, new_doc)
722
+ if success:
723
+ st.success(f"Created {new_doc['id']}")
724
+ st.rerun()
725
+ else:
726
+ st.error(message)
727
+ except Exception as e:
728
+ st.error(f"Create err: {str(e)}")
729
+ st.subheader(f"πŸ“Š {st.session_state.selected_container}")
730
+ if documents_to_display:
731
+ df = pd.DataFrame(documents_to_display)
732
+ st.dataframe(df)
733
+ else:
734
+ st.info("No docs.")
735
+ # --- End of Document UI ---
736
+ st.subheader("πŸ™ GitHub Ops")
737
+ github_token = os.environ.get("GITHUB")
738
+ source_repo = st.text_input("Source Repo URL", value="https://github.com/AaronCWacker/AIExamples-8-24-Streamlit")
739
+ new_repo_name = st.text_input("New Repo Name", value=f"Clone-{datetime.now().strftime('%Y%m%d_%H%M%S')}")
740
+ col_g1, col_g2 = st.columns(2)
741
+ with col_g1:
742
+ if st.button("πŸ“₯ Clone Repo"):
743
+ if github_token and source_repo:
744
+ try:
745
+ local_path = f"./temp_repo_{datetime.now().strftime('%Y%m%d%H%M%S')}"
746
+ download_github_repo(source_repo, local_path)
747
+ zip_filename = f"{new_repo_name}.zip"
748
+ create_zip_file(local_path, zip_filename[:-4])
749
+ st.markdown(get_download_link(zip_filename), unsafe_allow_html=True)
750
+ st.success("Cloned! πŸŽ‰")
751
+ except Exception as e:
752
+ st.error(f"Clone err: {str(e)}")
753
+ finally:
754
+ if os.path.exists(local_path):
755
+ shutil.rmtree(local_path)
756
+ if os.path.exists(zip_filename):
757
+ os.remove(zip_filename)
758
+ else:
759
+ st.error("Missing token or URL πŸ”‘β“")
760
+ with col_g2:
761
+ if st.button("πŸ“€ Push Repo"):
762
+ if github_token and source_repo:
763
+ try:
764
+ g = Github(github_token)
765
+ new_repo = create_repo(g, new_repo_name)
766
+ local_path = f"./temp_repo_{datetime.now().strftime('%Y%m%d%H%M%S')}"
767
+ download_github_repo(source_repo, local_path)
768
+ push_to_github(local_path, new_repo, github_token)
769
+ st.success(f"Pushed to {new_repo.html_url} πŸš€")
770
+ except Exception as e:
771
+ st.error(f"Push err: {str(e)}")
772
+ finally:
773
+ if os.path.exists(local_path):
774
+ shutil.rmtree(local_path)
775
+ else:
776
+ st.error("Missing token or URL πŸ”‘β“")
777
+ # --- File Management Section ---
778
+ update_file_management_section()
779
+ except exceptions.CosmosHttpResponseError as e:
780
+ st.error(f"Cosmos error: {str(e)} 🚨")
781
+ except Exception as e:
782
+ st.error(f"Error: {str(e)} 😱")
783
+ if st.session_state.logged_in and st.sidebar.button("πŸšͺ Logout"):
784
+ st.markdown("#### πŸšͺ Logout")
785
+ st.session_state.logged_in = False
786
+ st.session_state.selected_records = []
787
+ st.session_state.client = None
788
+ st.session_state.selected_database = None
789
+ st.session_state.selected_container = None
790
+ st.session_state.selected_document_id = None
791
+ st.session_state.current_index = 0
792
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
793
 
794
  if __name__ == "__main__":
795
+ main()