awacke1 commited on
Commit
e870d0a
Β·
verified Β·
1 Parent(s): 9bb8d5d

Update app.py

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