awacke1 commited on
Commit
396c7c1
Β·
verified Β·
1 Parent(s): bfce10c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +526 -94
app.py CHANGED
@@ -27,133 +27,569 @@ import io
27
  import requests
28
  import numpy as np
29
  from urllib.parse import quote
30
- import logging # Added for logging
31
 
32
- # Configure logging
33
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
34
- logger = logging.getLogger(__name__)
35
-
36
- # ... (Previous imports and external links remain unchanged)
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  # =============================================================================
39
  # ───────────── APP CONFIGURATION ─────────────
40
  # =============================================================================
41
- # ... (App configuration remains unchanged)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  # =============================================================================
44
  # ───────────── HELPER FUNCTIONS ─────────────
45
  # =============================================================================
46
- # ... (Previous helper functions remain unchanged)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  # =============================================================================
49
  # ───────────── COSMOS DB FUNCTIONS ─────────────
50
  # =============================================================================
51
- def vector_search(container, query, search_type="basic", limit=10):
52
- """Perform vector search on Cosmos DB container with different search types."""
 
 
 
 
 
 
 
 
 
 
53
  try:
54
- if search_type == "basic":
55
- sql_query = f"SELECT TOP {limit} * FROM c WHERE CONTAINS(c.content, '{query}', true) ORDER BY c._ts DESC"
56
- elif search_type == "exact":
57
- sql_query = f"SELECT TOP {limit} * FROM c WHERE c.content = '{query}' ORDER BY c._ts DESC"
58
- elif search_type == "prefix":
59
- sql_query = f"SELECT TOP {limit} * FROM c WHERE STARTSWITH(c.content, '{query}', true) ORDER BY c._ts DESC"
60
- else:
61
- raise ValueError("Unsupported search type")
62
-
63
- logger.info(f"Executing vector search with query: {sql_query}")
64
- items = list(container.query_items(query=sql_query, enable_cross_partition_query=True))
65
- return items, sql_query
 
66
  except Exception as e:
67
- logger.error(f"Vector search error: {str(e)}")
68
- return [], f"Error: {str(e)}"
69
 
70
  def delete_record(container, record):
71
- """Delete a record from Cosmos DB with detailed logging."""
72
  try:
73
  doc_id = record["id"]
74
  partition_key_value = record.get("pk", doc_id)
75
- logger.info(f"Attempting to delete record {doc_id} with partition key {partition_key_value}")
76
  container.delete_item(item=doc_id, partition_key=partition_key_value)
77
- logger.info(f"Successfully deleted record {doc_id}")
78
  return True, f"Record {doc_id} deleted. πŸ—‘οΈ"
79
- except exceptions.CosmosResourceNotFoundError as e:
80
- logger.warning(f"Record {doc_id} not found (already deleted): {str(e)}")
81
  return True, f"Record {doc_id} not found (already deleted). πŸ—‘οΈ"
82
  except exceptions.CosmosHttpResponseError as e:
83
- logger.error(f"HTTP error deleting {doc_id}: {str(e)}")
84
  return False, f"HTTP error deleting {doc_id}: {str(e)} 🚨"
85
  except Exception as e:
86
- logger.error(f"Unexpected error deleting {doc_id}: {str(e)}")
87
  return False, f"Unexpected error deleting {doc_id}: {str(e)} 😱"
88
 
89
- def import_database(client, file_path):
90
- """Import a Cosmos DB database from a zip file."""
91
  try:
92
- with zipfile.ZipFile(file_path, 'r') as zip_ref:
93
- zip_ref.extractall("./temp_import")
94
- for db_file in glob.glob("./temp_import/*.json"):
95
- with open(db_file, 'r') as f:
96
- db_data = json.load(f)
97
- db_id = db_data.get("id")
98
- if db_id:
99
- database = client.create_database_if_not_exists(id=db_id)
100
- for container_data in db_data.get("containers", []):
101
- container_id = container_data.get("id")
102
- partition_key = container_data.get("partitionKey", "/pk")
103
- container = database.create_container_if_not_exists(
104
- id=container_id, partition_key=PartitionKey(path=partition_key)
105
- )
106
- for item in container_data.get("items", []):
107
- container.upsert_item(item)
108
- shutil.rmtree("./temp_import")
109
- return True, "Database imported successfully! πŸ“₯"
110
  except Exception as e:
111
- logger.error(f"Import error: {str(e)}")
112
- return False, f"Import error: {str(e)} 😱"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- # ... (Other Cosmos DB functions remain unchanged)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  # =============================================================================
117
  # ───────────── UI FUNCTIONS ─────────────
118
  # =============================================================================
119
- def vector_search_ui(container):
120
- st.sidebar.markdown("### 🌐 Vector Search")
121
- st.sidebar.markdown("*Search documents using vector-based techniques in Cosmos DB.*")
122
- with st.sidebar.form("vector_search_form"):
123
- query = st.text_input("Enter search query", key="vector_query")
124
- search_type = st.selectbox("Search Type", ["basic", "exact", "prefix"], help="Basic: Contains keyword, Exact: Exact match, Prefix: Starts with")
125
- search_submitted = st.form_submit_button("πŸ” Run Search")
 
 
 
 
126
 
127
- if search_submitted and query:
128
- results, executed_query = vector_search(container, query, search_type)
129
- st.markdown("#### Vector Search Results")
130
- st.text(f"Executed Query: {executed_query}")
131
- if isinstance(results, list) and results:
132
- for doc in results:
133
- with st.expander(f"{doc.get('name', 'Unnamed')} - {doc.get('timestamp', 'No timestamp')}"):
134
- st.json(doc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  else:
136
- st.info("No results found or an error occurred.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- def import_export_ui(client, database_name, container_name):
139
- st.sidebar.markdown("### πŸ“¦ Import/Export DB")
140
- st.sidebar.markdown("*Easily import or export your Cosmos DB databases.*")
141
- col1, col2 = st.sidebar.columns(2)
142
  with col1:
143
- if st.button("πŸ“€ Export"):
144
- download_link = archive_current_container(database_name, container_name, client)
145
- st.sidebar.markdown(download_link, unsafe_allow_html=True) if download_link.startswith('<a') else st.sidebar.error(download_link)
146
  with col2:
147
- uploaded_file = st.file_uploader("πŸ“₯ Import", type=["zip"], key="import_file")
148
- if uploaded_file and st.button("Import Now"):
149
- success, message = import_database(client, uploaded_file)
150
- if success:
151
- st.sidebar.success(message)
152
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
153
  else:
154
- st.sidebar.error(message)
155
-
156
- # ... (Other UI functions remain unchanged)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  # =============================================================================
159
  # ───────────── MAIN FUNCTION ─────────────
@@ -175,14 +611,6 @@ def main():
175
  # Sidebar: Hierarchical Navigation
176
  st.sidebar.title("πŸ™ Navigator")
177
 
178
- # Vector Search (Moved to top)
179
- if st.session_state.current_container:
180
- vector_search_ui(st.session_state.current_container)
181
-
182
- # Import/Export (Second feature)
183
- if st.session_state.current_container:
184
- import_export_ui(st.session_state.client, st.session_state.selected_database, st.session_state.selected_container)
185
-
186
  # Databases Section
187
  st.sidebar.subheader("πŸ—ƒοΈ Databases")
188
  if "client" not in st.session_state:
@@ -221,6 +649,10 @@ def main():
221
 
222
  # Actions Section
223
  st.sidebar.subheader("βš™οΈ Actions")
 
 
 
 
224
 
225
  # Items Section
226
  st.sidebar.subheader("πŸ“‘ Items")
 
27
  import requests
28
  import numpy as np
29
  from urllib.parse import quote
 
30
 
31
+ # =============================================================================
32
+ # ───────────── EXTERNAL HELP LINKS ─────────────
33
+ # =============================================================================
34
+ external_links = [
35
+ {"title": "CosmosDB GenAI Full Text Search", "url": "https://learn.microsoft.com/en-us/azure/cosmos-db/gen-ai/full-text-search", "emoji": "πŸ’»"},
36
+ {"title": "CosmosDB SQL API Client Library", "url": "https://learn.microsoft.com/en-us/python/api/overview/azure/cosmos-readme?view=azure-python", "emoji": "πŸ’»"},
37
+ {"title": "CosmosDB Index and Query Vectors", "url": "https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/how-to-python-vector-index-query", "emoji": "πŸ’»"},
38
+ {"title": "CosmosDB NoSQL Materialized Views", "url": "https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/materialized-views", "emoji": "πŸ’»"},
39
+ {"title": "LangChain Vector Store Guide", "url": "https://python.langchain.com/docs/integrations/vectorstores/azure_cosmos_db_no_sql/", "emoji": "πŸ’»"},
40
+ {"title": "Vector Database Prompt Engineering RAG for Python", "url": "https://learn.microsoft.com/en-us/azure/cosmos-db/vector-database?source=recommendations", "emoji": "πŸ’»"},
41
+ {"title": "MergeKit Official GitHub", "url": "https://github.com/arcee-ai/MergeKit", "emoji": "πŸ’»"},
42
+ {"title": "MergeKit Sample Usage", "url": "https://github.com/arcee-ai/MergeKit#examples", "emoji": "πŸ“š"},
43
+ {"title": "DistillKit Official GitHub", "url": "https://github.com/arcee-ai/DistillKit", "emoji": "πŸ’»"},
44
+ {"title": "DistillKit Sample Usage", "url": "https://github.com/arcee-ai/DistillKit#usage", "emoji": "πŸ“š"},
45
+ {"title": "arcee.ai Official Website", "url": "https://arcee.ai", "emoji": "🌐"},
46
+ ]
47
 
48
  # =============================================================================
49
  # ───────────── APP CONFIGURATION ─────────────
50
  # =============================================================================
51
+ Site_Name = 'πŸ™ GitCosmos'
52
+ title = "πŸ™ GitCosmos"
53
+ helpURL = 'https://huggingface.co/awacke1'
54
+ bugURL = 'https://huggingface.co/spaces/awacke1/AzureCosmosDBUI/'
55
+ icons = 'πŸ™πŸŒŒπŸ’«'
56
+ st.set_page_config(
57
+ page_title=title,
58
+ page_icon=icons,
59
+ layout="wide",
60
+ initial_sidebar_state="auto",
61
+ menu_items={
62
+ 'Get Help': helpURL,
63
+ 'Report a bug': bugURL,
64
+ 'About': title
65
+ }
66
+ )
67
+
68
+ ENDPOINT = "https://acae-afd.documents.azure.com:443/"
69
+ DATABASE_NAME = os.environ.get("COSMOS_DATABASE_NAME")
70
+ CONTAINER_NAME = os.environ.get("COSMOS_CONTAINER_NAME")
71
+ Key = os.environ.get("Key")
72
+ CosmosDBUrl = 'https://portal.azure.com/#@AaronCWackergmail.onmicrosoft.com/resource/subscriptions/003fba60-5b3f-48f4-ab36-3ed11bc40816/resourceGroups/datasets/providers/Microsoft.DocumentDB/databaseAccounts/acae-afd/dataExplorer'
73
 
74
  # =============================================================================
75
  # ───────────── HELPER FUNCTIONS ─────────────
76
  # =============================================================================
77
+ def get_download_link(file_path):
78
+ with open(file_path, "rb") as file:
79
+ contents = file.read()
80
+ b64 = base64.b64encode(contents).decode()
81
+ file_name = os.path.basename(file_path)
82
+ return f'<a href="data:file/txt;base64,{b64}" download="{file_name}">Download {file_name} πŸ“‚</a>'
83
+
84
+ def generate_unique_id():
85
+ timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
86
+ unique_uuid = str(uuid.uuid4())
87
+ return f"{timestamp}-{unique_uuid}"
88
+
89
+ def generate_filename(prompt, file_type):
90
+ central = pytz.timezone('US/Central')
91
+ safe_date_time = datetime.now(central).strftime("%m%d_%H%M")
92
+ safe_prompt = re.sub(r'\W+', '', prompt)[:90]
93
+ return f"{safe_date_time}{safe_prompt}.{file_type}"
94
+
95
+ def create_file(filename, prompt, response, should_save=True):
96
+ if should_save:
97
+ with open(filename, 'w', encoding='utf-8') as file:
98
+ file.write(prompt + "\n\n" + response)
99
+
100
+ def load_file(file_name):
101
+ with open(file_name, "r", encoding='utf-8') as file:
102
+ return file.read()
103
+
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
+ def preprocess_text(text):
112
+ text = text.replace('\r\n', '\\n').replace('\r', '\\n').replace('\n', '\\n')
113
+ text = text.replace('"', '\\"')
114
+ text = re.sub(r'[\t]', ' ', text)
115
+ text = re.sub(r'[^\x00-\x7F]+', '', text)
116
+ return text.strip()
117
+
118
+ def sanitize_json_text(text):
119
+ text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F]', '', text)
120
+ return text.strip()
121
 
122
  # =============================================================================
123
  # ───────────── COSMOS DB FUNCTIONS ─────────────
124
  # =============================================================================
125
+ def get_databases(client):
126
+ return [db['id'] for db in client.list_databases()]
127
+
128
+ def get_containers(database):
129
+ return [container['id'] for container in database.list_containers()]
130
+
131
+ def get_documents(container, limit=None):
132
+ query = "SELECT * FROM c ORDER BY c._ts DESC"
133
+ items = list(container.query_items(query=query, enable_cross_partition_query=True, max_item_count=limit))
134
+ return items
135
+
136
+ def insert_record(container, record):
137
  try:
138
+ container.create_item(body=record)
139
+ return True, "Inserted! πŸŽ‰"
140
+ except exceptions.CosmosHttpResponseError as e:
141
+ return False, f"HTTP error: {str(e)} 🚨"
142
+ except Exception as e:
143
+ return False, f"Error: {str(e)} 😱"
144
+
145
+ def update_record(container, updated_record):
146
+ try:
147
+ container.upsert_item(body=updated_record)
148
+ return True, f"Updated {updated_record['id']} πŸ› οΈ"
149
+ except exceptions.CosmosHttpResponseError as e:
150
+ return False, f"HTTP error: {str(e)} 🚨"
151
  except Exception as e:
152
+ return False, f"Error: {str(e)} 😱"
 
153
 
154
  def delete_record(container, record):
 
155
  try:
156
  doc_id = record["id"]
157
  partition_key_value = record.get("pk", doc_id)
158
+ st.write(f"Deleting {doc_id} with partition key {partition_key_value}")
159
  container.delete_item(item=doc_id, partition_key=partition_key_value)
 
160
  return True, f"Record {doc_id} deleted. πŸ—‘οΈ"
161
+ except exceptions.CosmosResourceNotFoundError:
 
162
  return True, f"Record {doc_id} not found (already deleted). πŸ—‘οΈ"
163
  except exceptions.CosmosHttpResponseError as e:
 
164
  return False, f"HTTP error deleting {doc_id}: {str(e)} 🚨"
165
  except Exception as e:
 
166
  return False, f"Unexpected error deleting {doc_id}: {str(e)} 😱"
167
 
168
+ def save_to_cosmos_db(container, query, response1, response2):
 
169
  try:
170
+ timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
171
+ unique_uuid = str(uuid.uuid4())
172
+ new_id = f"{timestamp}-{unique_uuid}"
173
+ record = {
174
+ "id": new_id,
175
+ "pk": new_id,
176
+ "name": new_id,
177
+ "query": query,
178
+ "response1": response1,
179
+ "response2": response2,
180
+ "timestamp": datetime.utcnow().isoformat(),
181
+ "type": "ai_response",
182
+ "version": "1.0"
183
+ }
184
+ container.create_item(body=record)
185
+ st.success(f"Saved: {record['id']}")
 
 
186
  except Exception as e:
187
+ st.error(f"Save error: {str(e)}")
188
+
189
+ def archive_current_container(database_name, container_name, client):
190
+ try:
191
+ base_dir = "./cosmos_archive"
192
+ if os.path.exists(base_dir):
193
+ shutil.rmtree(base_dir)
194
+ os.makedirs(base_dir)
195
+ db_client = client.get_database_client(database_name)
196
+ container_client = db_client.get_container_client(container_name)
197
+ items = list(container_client.read_all_items())
198
+ container_dir = os.path.join(base_dir, container_name)
199
+ os.makedirs(container_dir)
200
+ for item in items:
201
+ item_id = item.get('id', f"unknown_{datetime.now().strftime('%Y%m%d%H%M%S')}")
202
+ with open(os.path.join(container_dir, f"{item_id}.json"), 'w') as f:
203
+ json.dump(item, f, indent=2)
204
+ archive_name = f"{container_name}_archive_{datetime.now().strftime('%Y%m%d%H%M%S')}"
205
+ shutil.make_archive(archive_name, 'zip', base_dir)
206
+ return get_download_link(f"{archive_name}.zip")
207
+ except Exception as e:
208
+ return f"Archive error: {str(e)} 😒"
209
+
210
+ def create_new_container(database, container_id, partition_key_path):
211
+ try:
212
+ container = database.create_container(
213
+ id=container_id,
214
+ partition_key=PartitionKey(path=partition_key_path)
215
+ )
216
+ return container
217
+ except exceptions.CosmosResourceExistsError:
218
+ return database.get_container_client(container_id)
219
+ except exceptions.CosmosHttpResponseError as e:
220
+ st.error(f"Error creating container: {str(e)}")
221
+ return None
222
+
223
+ # =============================================================================
224
+ # ───────────── GITHUB FUNCTIONS ─────────────
225
+ # =============================================================================
226
+ def download_github_repo(url, local_path):
227
+ if os.path.exists(local_path):
228
+ shutil.rmtree(local_path)
229
+ Repo.clone_from(url, local_path)
230
+
231
+ def create_zip_file(source_dir, output_filename):
232
+ shutil.make_archive(output_filename, 'zip', source_dir)
233
+
234
+ def create_repo(g, repo_name):
235
+ user = g.get_user()
236
+ return user.create_repo(repo_name)
237
+
238
+ def push_to_github(local_path, repo, github_token):
239
+ repo_url = f"https://{github_token}@github.com/{repo.full_name}.git"
240
+ local_repo = Repo(local_path)
241
+ if 'origin' in [remote.name for remote in local_repo.remotes]:
242
+ origin = local_repo.remote('origin')
243
+ origin.set_url(repo_url)
244
+ else:
245
+ origin = local_repo.create_remote('origin', repo_url)
246
+ if not local_repo.heads:
247
+ local_repo.git.checkout('-b', 'main')
248
+ current_branch = 'main'
249
+ else:
250
+ current_branch = local_repo.active_branch.name
251
+ local_repo.git.add(A=True)
252
+ if local_repo.is_dirty():
253
+ local_repo.git.commit('-m', 'Initial commit')
254
+ origin.push(refspec=f'{current_branch}:{current_branch}')
255
+
256
+ # =============================================================================
257
+ # ───────────── FILE & MEDIA MANAGEMENT FUNCTIONS ─────────────
258
+ # =============================================================================
259
+ def display_file_viewer(file_path):
260
+ content = load_file(file_path)
261
+ if content:
262
+ st.markdown("### πŸ“„ File Viewer")
263
+ st.markdown(f"**{file_path}**")
264
+ file_stats = os.stat(file_path)
265
+ st.markdown(f"**Mod:** {datetime.fromtimestamp(file_stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')} | **Size:** {file_stats.st_size} bytes")
266
+ st.markdown("---")
267
+ st.markdown(content)
268
+ st.download_button("⬇️", data=content, file_name=os.path.basename(file_path), mime="text/markdown")
269
+
270
+ def display_file_editor(file_path):
271
+ if 'file_content' not in st.session_state:
272
+ st.session_state.file_content = {}
273
+ if file_path not in st.session_state.file_content:
274
+ content = load_file(file_path)
275
+ if content:
276
+ st.session_state.file_content[file_path] = content
277
+ st.markdown("### ✏️ Edit File")
278
+ st.markdown(f"**Editing:** {file_path}")
279
+ new_content = st.text_area("Edit:", value=st.session_state.file_content.get(file_path, ""), height=400, key=f"editor_{hash(file_path)}")
280
+ col1, col2 = st.columns([1, 5])
281
+ with col1:
282
+ if st.button("πŸ’Ύ Save"):
283
+ sanitized = sanitize_json_text(new_content)
284
+ try:
285
+ json.loads(sanitized)
286
+ with open(file_path, 'w', encoding='utf-8') as file:
287
+ file.write(sanitized)
288
+ st.session_state.file_content[file_path] = sanitized
289
+ st.success("Saved! πŸŽ‰")
290
+ time.sleep(1)
291
+ st.rerun()
292
+ except Exception as e:
293
+ st.error(f"Save error: {str(e)}")
294
+ with col2:
295
+ st.download_button("⬇️", data=new_content, file_name=os.path.basename(file_path), mime="text/markdown")
296
 
297
+ def update_file_management_section():
298
+ if 'file_view_mode' not in st.session_state:
299
+ st.session_state.file_view_mode = None
300
+ if 'current_file' not in st.session_state:
301
+ st.session_state.current_file = None
302
+ all_files = sorted(glob.glob("*.md"), reverse=True)
303
+ st.sidebar.subheader("πŸ“ Files")
304
+ if st.sidebar.button("πŸ—‘ Delete All"):
305
+ for file in all_files:
306
+ os.remove(file)
307
+ st.session_state.file_content = {}
308
+ st.session_state.current_file = None
309
+ st.session_state.file_view_mode = None
310
+ st.rerun()
311
+ if st.sidebar.button("⬇️ Download All"):
312
+ zip_file = create_zip_of_files(all_files)
313
+ st.sidebar.markdown(get_download_link(zip_file), unsafe_allow_html=True)
314
+ for file in all_files:
315
+ col1, col2, col3, col4 = st.sidebar.columns([1, 3, 1, 1])
316
+ with col1:
317
+ if st.button("🌐", key=f"view_{file}"):
318
+ st.session_state.current_file = file
319
+ st.session_state.file_view_mode = 'view'
320
+ st.rerun()
321
+ with col2:
322
+ st.markdown(get_download_link(file), unsafe_allow_html=True)
323
+ with col3:
324
+ if st.button("πŸ“‚", key=f"edit_{file}"):
325
+ st.session_state.current_file = file
326
+ st.session_state.file_view_mode = 'edit'
327
+ st.rerun()
328
+ with col4:
329
+ if st.button("πŸ—‘", key=f"delete_{file}"):
330
+ os.remove(file)
331
+ if file in st.session_state.file_content:
332
+ del st.session_state.file_content[file]
333
+ if st.session_state.current_file == file:
334
+ st.session_state.current_file = None
335
+ st.session_state.file_view_mode = None
336
+ st.rerun()
337
+ st.sidebar.subheader("External Help Links")
338
+ for link in external_links:
339
+ st.sidebar.markdown(f"{link['emoji']} [{link['title']}]({link['url']})", unsafe_allow_html=True)
340
+ if st.session_state.current_file:
341
+ if st.session_state.file_view_mode == 'view':
342
+ display_file_viewer(st.session_state.current_file)
343
+ elif st.session_state.file_view_mode == 'edit':
344
+ display_file_editor(st.session_state.current_file)
345
 
346
  # =============================================================================
347
  # ───────────── UI FUNCTIONS ─────────────
348
  # =============================================================================
349
+ def edit_all_documents(container, search_keyword=None):
350
+ st.markdown("### πŸ“‘ All Documents" + (f" (Filtered: '{search_keyword}')" if search_keyword else ""))
351
+ documents = get_documents(container)
352
+ if search_keyword:
353
+ documents = [doc for doc in documents if vector_keyword_search(search_keyword, doc)]
354
+ if not documents:
355
+ st.info("No documents match the current filter." if search_keyword else "No documents in this container.")
356
+ return
357
+
358
+ if 'saved_docs' not in st.session_state:
359
+ st.session_state.saved_docs = {}
360
 
361
+ for doc in documents:
362
+ ts = doc.get("_ts", 0)
363
+ dt = datetime.fromtimestamp(ts) if ts else datetime.now()
364
+ formatted_ts = dt.strftime("%I:%M %p %m/%d/%Y")
365
+ header = f"{doc.get('name', 'Unnamed')} - {formatted_ts}"
366
+ with st.expander(header):
367
+ doc_key = f"editor_{doc['id']}"
368
+ initial_value = st.session_state.saved_docs.get(doc['id'], json.dumps(doc, indent=2))
369
+ edited_content = st.text_area("Edit JSON", value=initial_value, height=300, key=doc_key)
370
+ col_save, col_delete = st.columns(2)
371
+ with col_save:
372
+ if st.button("πŸ’Ύ Save", key=f"save_{doc['id']}"):
373
+ try:
374
+ cleaned_content = sanitize_json_text(edited_content)
375
+ updated_doc = json.loads(cleaned_content)
376
+ updated_doc['id'] = doc['id']
377
+ updated_doc['pk'] = doc.get('pk', doc['id'])
378
+ for field in ['_ts', '_rid', '_self', '_etag', '_attachments']:
379
+ updated_doc.pop(field, None)
380
+ success, message = update_record(container, updated_doc)
381
+ if success:
382
+ st.success(f"Saved {doc['id']}")
383
+ st.session_state.saved_docs[doc['id']] = json.dumps(updated_doc, indent=2)
384
+ st.rerun()
385
+ else:
386
+ st.error(message)
387
+ except json.JSONDecodeError as e:
388
+ st.error(f"Invalid JSON format: {str(e)}\nProblematic input:\n{cleaned_content}")
389
+ except Exception as e:
390
+ st.error(f"Save error: {str(e)}")
391
+ with col_delete:
392
+ if st.button("πŸ—‘οΈ Delete", key=f"delete_{doc['id']}"):
393
+ success, message = delete_record(container, doc)
394
+ if success:
395
+ st.success(message)
396
+ if doc['id'] in st.session_state.saved_docs:
397
+ del st.session_state.saved_docs[doc['id']]
398
+ st.rerun()
399
+ else:
400
+ st.error(message)
401
+
402
+ def new_item_default(container):
403
+ new_id = generate_unique_id()
404
+ default_doc = {
405
+ "id": new_id,
406
+ "pk": new_id,
407
+ "name": "New Document",
408
+ "content": "Start editing here...",
409
+ "timestamp": datetime.now().isoformat(),
410
+ "type": "sample"
411
+ }
412
+ success, message = insert_record(container, default_doc)
413
+ if success:
414
+ st.success("New document created! ✨")
415
+ st.rerun()
416
+ else:
417
+ st.error(f"Error creating new item: {message}")
418
+
419
+ def add_field_to_doc():
420
+ key = st.session_state.new_field_key
421
+ value = st.session_state.new_field_value
422
+ try:
423
+ doc = json.loads(st.session_state.doc_editor)
424
+ doc[key] = value
425
+ st.session_state.doc_editor = json.dumps(doc, indent=2)
426
+ container = st.session_state.current_container
427
+ success, message = update_record(container, doc)
428
+ if success:
429
+ st.success(f"Added field {key} πŸ‘")
430
+ else:
431
+ st.error(message)
432
+ except Exception as e:
433
+ st.error(f"Error adding field: {str(e)}")
434
+
435
+ def new_ai_record(container):
436
+ new_id = generate_unique_id()
437
+ default_doc = {
438
+ "id": new_id,
439
+ "pk": new_id,
440
+ "name": "AI Modality Record",
441
+ "function_url": "https://example.com/function",
442
+ "input_text": "### Input (markdown)\n\nType your input here.",
443
+ "output_text": "### Output (markdown)\n\nResult will appear here.",
444
+ "timestamp": datetime.now().isoformat(),
445
+ "type": "ai_modality"
446
+ }
447
+ success, message = insert_record(container, default_doc)
448
+ if success:
449
+ st.success("New AI modality record created! πŸ’‘")
450
+ st.rerun()
451
+ else:
452
+ st.error(f"Error creating AI record: {message}")
453
+
454
+ def new_links_record(container):
455
+ new_id = generate_unique_id()
456
+ links_md = "\n".join([f"- {link['emoji']} [{link['title']}]({link['url']})" for link in external_links])
457
+ default_doc = {
458
+ "id": new_id,
459
+ "pk": new_id,
460
+ "name": "Portal Links Record",
461
+ "function_url": "",
462
+ "input_text": links_md,
463
+ "output_text": "",
464
+ "timestamp": datetime.now().isoformat(),
465
+ "type": "ai_modality"
466
+ }
467
+ success, message = insert_record(container, default_doc)
468
+ if success:
469
+ st.success("New Portal Links record created! πŸ”—")
470
+ st.rerun()
471
+ else:
472
+ st.error(f"Error creating links record: {message}")
473
+
474
+ def vector_keyword_search(keyword, doc):
475
+ keyword = keyword.lower()
476
+ for key, value in doc.items():
477
+ if isinstance(value, str) and keyword in value.lower():
478
+ return True
479
+ return False
480
+
481
+ def search_documents_ui(container):
482
+ with st.sidebar.form("search_form"):
483
+ keyword = st.text_input("Search Keyword", key="search_keyword")
484
+ col1, col2 = st.columns(2)
485
+ with col1:
486
+ search_submitted = st.form_submit_button("πŸ” Search")
487
+ with col2:
488
+ clear_submitted = st.form_submit_button("πŸ—‘οΈ Clear")
489
+ if search_submitted and keyword:
490
+ st.session_state.active_search = keyword # Use a separate key
491
+ st.rerun()
492
+ if clear_submitted:
493
+ if 'active_search' in st.session_state:
494
+ del st.session_state.active_search
495
+ st.rerun()
496
+
497
+ def validate_and_preprocess_image(file_data, target_size=(576, 1024)):
498
+ try:
499
+ if isinstance(file_data, bytes):
500
+ img = Image.open(io.BytesIO(file_data))
501
+ elif hasattr(file_data, 'read'):
502
+ if hasattr(file_data, 'seek'):
503
+ file_data.seek(0)
504
+ img = Image.open(file_data)
505
  else:
506
+ raise ValueError(f"Unsupported input: {type(file_data)}")
507
+ if img.mode != 'RGB':
508
+ img = img.convert('RGB')
509
+ aspect_ratio = img.size[0] / img.size[1]
510
+ if aspect_ratio > target_size[0] / target_size[1]:
511
+ new_width = target_size[0]
512
+ new_height = int(new_width / aspect_ratio)
513
+ else:
514
+ new_height = target_size[1]
515
+ new_width = int(new_height * aspect_ratio)
516
+ new_width = (new_width // 2) * 2
517
+ new_height = (new_height // 2) * 2
518
+ resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
519
+ final_img = Image.new('RGB', target_size, (255, 255, 255))
520
+ paste_x = (target_size[0] - new_width) // 2
521
+ paste_y = (target_size[1] - new_height) // 2
522
+ final_img.paste(resized_img, (paste_x, paste_y))
523
+ return final_img
524
+ except Exception as e:
525
+ st.error(f"Image error: {str(e)}")
526
+ return None
527
 
528
+ def add_video_generation_ui(container):
529
+ st.markdown("### πŸŽ₯ Video Generation")
530
+ col1, col2 = st.columns([2, 1])
 
531
  with col1:
532
+ uploaded_file = st.file_uploader("Upload Image πŸ–ΌοΈ", type=['png', 'jpg', 'jpeg'])
 
 
533
  with col2:
534
+ st.markdown("#### Parameters")
535
+ motion = st.slider("🌊 Motion", 1, 255, 127)
536
+ fps = st.slider("🎬 FPS", 1, 30, 6)
537
+ with st.expander("Advanced"):
538
+ use_custom = st.checkbox("Custom Seed")
539
+ seed = st.number_input("Seed", value=int(time.time() * 1000)) if use_custom else None
540
+ if uploaded_file:
541
+ file_data = uploaded_file.read()
542
+ preview1, preview2 = st.columns(2)
543
+ with preview1:
544
+ st.write("Original")
545
+ st.image(Image.open(io.BytesIO(file_data)), use_column_width=True)
546
+ with preview2:
547
+ proc_img = validate_and_preprocess_image(io.BytesIO(file_data))
548
+ if proc_img:
549
+ st.write("Processed")
550
+ st.image(proc_img, use_column_width=True)
551
  else:
552
+ return
553
+ if st.button("πŸŽ₯ Generate"):
554
+ with st.spinner("Generating video..."):
555
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as temp_file:
556
+ proc_img.save(temp_file.name, format='PNG')
557
+ try:
558
+ client = Client("awacke1/stable-video-diffusion", hf_token=os.environ.get("HUGGINGFACE_TOKEN"))
559
+ result = client.predict(
560
+ image=temp_file.name,
561
+ seed=seed if seed is not None else int(time.time() * 1000),
562
+ randomize_seed=seed is None,
563
+ motion_bucket_id=motion,
564
+ fps_id=fps,
565
+ api_name="/video"
566
+ )
567
+ video_path = result[0].get('video') if isinstance(result[0], dict) else None
568
+ if video_path and os.path.exists(video_path):
569
+ video_filename = f"generated_video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
570
+ shutil.copy(video_path, video_filename)
571
+ st.success(f"Video generated! πŸŽ‰")
572
+ st.video(video_filename)
573
+ if container:
574
+ video_record = {
575
+ "id": generate_unique_id(),
576
+ "pk": generate_unique_id(),
577
+ "type": "generated_video",
578
+ "filename": video_filename,
579
+ "seed": seed if seed is not None else "random",
580
+ "motion": motion,
581
+ "fps": fps,
582
+ "timestamp": datetime.now().isoformat()
583
+ }
584
+ success, message = insert_record(container, video_record)
585
+ if success:
586
+ st.success("DB record saved!")
587
+ else:
588
+ st.error(f"DB error: {message}")
589
+ except Exception as e:
590
+ st.error(f"Video gen error: {str(e)}")
591
+ finally:
592
+ os.unlink(temp_file.name)
593
 
594
  # =============================================================================
595
  # ───────────── MAIN FUNCTION ─────────────
 
611
  # Sidebar: Hierarchical Navigation
612
  st.sidebar.title("πŸ™ Navigator")
613
 
 
 
 
 
 
 
 
 
614
  # Databases Section
615
  st.sidebar.subheader("πŸ—ƒοΈ Databases")
616
  if "client" not in st.session_state:
 
649
 
650
  # Actions Section
651
  st.sidebar.subheader("βš™οΈ Actions")
652
+ if st.session_state.current_container:
653
+ if st.sidebar.button("πŸ“¦ Export Container"):
654
+ download_link = archive_current_container(st.session_state.selected_database, st.session_state.selected_container, st.session_state.client)
655
+ st.sidebar.markdown(download_link, unsafe_allow_html=True) if download_link.startswith('<a') else st.sidebar.error(download_link)
656
 
657
  # Items Section
658
  st.sidebar.subheader("πŸ“‘ Items")