awacke1 commited on
Commit
08f9116
·
verified ·
1 Parent(s): b1d0519

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +610 -105
app.py CHANGED
@@ -9,14 +9,421 @@ import zipfile
9
  import random
10
  import requests
11
  import openai
12
-
13
  from PIL import Image
14
  from urllib.parse import quote
15
 
16
  import streamlit as st
17
  import streamlit.components.v1 as components
18
 
19
- # Example base snippet: your normal Mermaid code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  DEFAULT_MERMAID = r"""
21
  flowchart LR
22
  U((User 😎)) -- "Talk 🗣️" --> LLM[LLM Agent 🤖\nExtract Info]
@@ -33,17 +440,19 @@ flowchart LR
33
  click KG "/?q=Knowledge%20Graph%20Ontology+GAR+RAG" "Open Knowledge Graph" "_blank"
34
  """
35
 
 
 
 
 
36
  def parse_mermaid_edges(mermaid_text: str):
37
  """
38
  🍿 parse_mermaid_edges:
39
- - Find lines like `A -- "Label" --> B`.
40
- - Return adjacency dict: edges[A] = [(label, B), ...].
41
  """
42
  adjacency = {}
43
- # Regex to match lines like: A -- "Label" --> B
44
  edge_pattern = re.compile(r'(\S+)\s*--\s*"([^"]*)"\s*-->\s*(\S+)')
45
-
46
- # We split the text into lines and search for edges
47
  for line in mermaid_text.split('\n'):
48
  match = edge_pattern.search(line.strip())
49
  if match:
@@ -51,132 +460,228 @@ def parse_mermaid_edges(mermaid_text: str):
51
  if nodeA not in adjacency:
52
  adjacency[nodeA] = []
53
  adjacency[nodeA].append((label, nodeB))
54
-
55
  return adjacency
56
 
57
- def build_subgraph(adjacency, start_node):
58
  """
59
- 🍎 build_subgraph:
60
- - BFS or DFS from start_node to gather edges.
61
- - For simplicity, we only gather direct edges from this node.
62
- - If you want a multi-level downstream search, do a BFS/DFS deeper.
63
  """
64
- sub_edges = []
65
- # If start_node has no adjacency, return empty
66
- if start_node not in adjacency:
67
- return sub_edges
68
-
69
- # For each edge out of start_node, store it in sub_edges
70
- for label, child in adjacency[start_node]:
71
- sub_edges.append((start_node, label, child))
72
 
73
- return sub_edges
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  def create_subgraph_mermaid(sub_edges, start_node):
76
  """
77
  🍄 create_subgraph_mermaid:
78
- - Given a list of edges in form (A, label, B),
79
- - Return a smaller flowchart snippet that includes them.
80
  """
81
- # Start with the flowchart directive
82
  sub_mermaid = "flowchart LR\n"
83
- sub_mermaid += f" %% Subgraph for {start_node}\n"
84
-
85
- # For each edge, build a line: NodeA -- "Label" --> NodeB
86
  for (A, label, B) in sub_edges:
87
- # Potentially you can keep the original styles or shapes (like U((User 😎))).
88
- # If your original code has shapes, you can store them in a dict so A-> "U((User 😎))" etc.
89
  sub_mermaid += f' {A} -- "{label}" --> {B}\n'
90
-
91
- # Optionally add a comment to show the subgraph ends
92
- sub_mermaid += f" %% End of subgraph for {start_node}\n"
93
  return sub_mermaid
94
 
95
- def generate_mermaid_html(mermaid_code: str) -> str:
96
- """Tiny function to embed Mermaid code in HTML with a CDN."""
97
- return f"""
98
- <html>
99
- <head>
100
- <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
101
- <style>
102
- .centered-mermaid {{
103
- display: flex;
104
- justify-content: center;
105
- margin: 20px auto;
106
- }}
107
- .mermaid {{
108
- max-width: 800px;
109
- }}
110
- </style>
111
- </head>
112
- <body>
113
- <div class="mermaid centered-mermaid">
114
- {mermaid_code}
115
- </div>
116
- <script>
117
- mermaid.initialize({{ startOnLoad: true }});
118
- </script>
119
- </body>
120
- </html>
121
- """
122
 
 
 
 
123
  def main():
124
- st.set_page_config(page_title="Partial Subgraph Demo", layout="wide")
125
- st.title("Partial Mermaid Subgraph from a Clicked Node")
 
 
 
 
126
 
127
- # 1) Show the main diagram
128
- st.subheader("Full Diagram:")
129
- full_html = generate_mermaid_html(DEFAULT_MERMAID)
130
- components.html(full_html, height=400, scrolling=True)
 
 
 
 
 
131
 
132
- # 2) Build adjacency from the original code
133
- adjacency = parse_mermaid_edges(DEFAULT_MERMAID)
 
 
134
 
135
- # 3) See if user clicked a shape, e.g. ?q=LLM%20Agent%20Extract%20Info
136
- query_params = st.query_params
137
- clicked = (query_params.get('q') or [""])[0] # If present
138
-
139
- if clicked:
140
- # The "clicked" node might contain spaces or special chars.
141
- # We typically match the "nodeId" from the code.
142
- # But your code might have the real node name "LLM" or "RE", etc.
143
- # If your node is "LLM" and the label is "LLM Agent Extract Info",
144
- # you might need to map them.
145
- # For simplicity, let's assume your node ID is the same as the label
146
- # after removing spaces.
147
- # Or if your original code uses node IDs like 'LLM' or 'U'.
148
- # We'll show an example:
149
-
150
- # We can guess your node ID might be "LLM" if the text includes "LLM".
151
- # Let's try a simpler approach: you store an internal mapping
152
- # from node ID -> label. We'll do a brute force approach:
153
- st.info(f"User clicked shape: {clicked}")
154
-
155
- # Suppose we want to find the adjacency key that best matches the clicked string:
156
- # This is a naive approach:
157
- # We'll see if 'clicked' is a substring of the adjacency's node key.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  possible_keys = []
159
  for nodeKey in adjacency.keys():
160
- if clicked.lower().replace("%20", " ").replace("extract info", "") in nodeKey.lower():
 
 
 
 
161
  possible_keys.append(nodeKey)
162
- # If we found one or more possible matches, take the first
163
  if possible_keys:
164
  chosen_node = possible_keys[0]
165
- # Build subgraph from adjacency
166
- sub_edges = build_subgraph(adjacency, chosen_node)
167
  if sub_edges:
168
  sub_mermaid = create_subgraph_mermaid(sub_edges, chosen_node)
169
-
170
- # Display top-centered subgraph
171
- st.subheader(f"SearchResult Subgraph for Node: {chosen_node}")
172
- partial_html = generate_mermaid_html(sub_mermaid)
173
- components.html(partial_html, height=300, scrolling=False)
174
- else:
175
- st.warning(f"No outgoing edges from node '{chosen_node}'.")
176
  else:
177
- st.warning("No matching node found in adjacency for that query param.")
178
- else:
179
- st.info("No shape clicked, or no ?q= in the query parameters.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
 
182
  if __name__ == "__main__":
 
9
  import random
10
  import requests
11
  import openai
 
12
  from PIL import Image
13
  from urllib.parse import quote
14
 
15
  import streamlit as st
16
  import streamlit.components.v1 as components
17
 
18
+ # 🏰 If you do model inference via huggingface_hub
19
+ # from huggingface_hub import InferenceClient
20
+
21
+ # =====================================================================================
22
+ # 1) GLOBAL CONFIG & PLACEHOLDERS
23
+ # =====================================================================================
24
+ BASE_URL = "https://huggingface.co/spaces/awacke1/MermaidMarkdownDiagramEditor"
25
+
26
+ PromptPrefix = "AI-Search: "
27
+ PromptPrefix2 = "AI-Refine: "
28
+ PromptPrefix3 = "AI-JS: "
29
+
30
+ roleplaying_glossary = {
31
+ "Core Rulebooks": {
32
+ "Dungeons and Dragons": ["Player's Handbook", "Dungeon Master's Guide", "Monster Manual"],
33
+ "GURPS": ["Basic Set Characters", "Basic Set Campaigns"]
34
+ },
35
+ "Campaigns & Adventures": {
36
+ "Pathfinder": ["Rise of the Runelords", "Curse of the Crimson Throne"]
37
+ }
38
+ }
39
+
40
+ transhuman_glossary = {
41
+ "Neural Interfaces": ["Cortex Jack", "Mind-Machine Fusion"],
42
+ "Cybernetics": ["Robotic Limbs", "Augmented Eyes"],
43
+ }
44
+
45
+ def process_text(text):
46
+ """🕵️ process_text: detective style—prints lines to Streamlit for debugging."""
47
+ st.write(f"process_text called with: {text}")
48
+
49
+ def search_arxiv(text):
50
+ """🔭 search_arxiv: pretend to search ArXiv, just prints debug for now."""
51
+ st.write(f"search_arxiv called with: {text}")
52
+
53
+ def SpeechSynthesis(text):
54
+ """🗣 SpeechSynthesis: read lines out loud? Here, we log them for demonstration."""
55
+ st.write(f"SpeechSynthesis called with: {text}")
56
+
57
+ def process_image(image_file, prompt):
58
+ """📷 process_image: imagine an AI pipeline for images, here we just log."""
59
+ return f"[process_image placeholder] {image_file} => {prompt}"
60
+
61
+ def process_video(video_file, seconds_per_frame):
62
+ """🎞 process_video: placeholder for video tasks, logs to Streamlit."""
63
+ st.write(f"[process_video placeholder] {video_file}, {seconds_per_frame} sec/frame")
64
+
65
+ API_URL = "https://huggingface-inference-endpoint-placeholder"
66
+ API_KEY = "hf_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
67
+
68
+ @st.cache_resource
69
+ def InferenceLLM(prompt):
70
+ """🔮 InferenceLLM: a stub returning a mock response for 'prompt'."""
71
+ return f"[InferenceLLM placeholder response to prompt: {prompt}]"
72
+
73
+
74
+ # =====================================================================================
75
+ # 2) GLOSSARY & FILE UTILITY
76
+ # =====================================================================================
77
+ @st.cache_resource
78
+ def display_glossary_entity(k):
79
+ """
80
+ Creates multiple link emojis for a single entity.
81
+ Each link might point to /?q=..., /?q=<prefix>..., or external sites.
82
+ """
83
+ search_urls = {
84
+ "🚀🌌ArXiv": lambda x: f"/?q={quote(x)}",
85
+ "🃏Analyst": lambda x: f"/?q={quote(x)}-{quote(PromptPrefix)}",
86
+ "📚PyCoder": lambda x: f"/?q={quote(x)}-{quote(PromptPrefix2)}",
87
+ "🔬JSCoder": lambda x: f"/?q={quote(x)}-{quote(PromptPrefix3)}",
88
+ "📖": lambda x: f"https://en.wikipedia.org/wiki/{quote(x)}",
89
+ "🔍": lambda x: f"https://www.google.com/search?q={quote(x)}",
90
+ "🔎": lambda x: f"https://www.bing.com/search?q={quote(x)}",
91
+ "🎥": lambda x: f"https://www.youtube.com/results?search_query={quote(x)}",
92
+ "🐦": lambda x: f"https://twitter.com/search?q={quote(x)}",
93
+ }
94
+ links_md = ' '.join([f"[{emoji}]({url(k)})" for emoji, url in search_urls.items()])
95
+ st.markdown(f"**{k}** <small>{links_md}</small>", unsafe_allow_html=True)
96
+
97
+ def display_content_or_image(query):
98
+ """
99
+ If 'query' is in transhuman_glossary or there's an image matching 'images/<query>.png',
100
+ we show it. Otherwise warn.
101
+ """
102
+ for category, term_list in transhuman_glossary.items():
103
+ for term in term_list:
104
+ if query.lower() in term.lower():
105
+ st.subheader(f"Found in {category}:")
106
+ st.write(term)
107
+ return True
108
+ image_path = f"images/{query}.png"
109
+ if os.path.exists(image_path):
110
+ st.image(image_path, caption=f"Image for {query}")
111
+ return True
112
+ st.warning("No matching content or image found.")
113
+ return False
114
+
115
+ def clear_query_params():
116
+ """For fully clearing, you'd do a redirect or st.experimental_set_query_params()."""
117
+ st.warning("Define a redirect or link without query params if you want to truly clear them.")
118
+
119
+
120
+ # =====================================================================================
121
+ # 3) FILE-HANDLING (MD files, etc.)
122
+ # =====================================================================================
123
+ def load_file(file_path):
124
+ """Load file contents as UTF-8 text, or return empty on error."""
125
+ try:
126
+ with open(file_path, "r", encoding='utf-8') as f:
127
+ return f.read()
128
+ except:
129
+ return ""
130
+
131
+ @st.cache_resource
132
+ def create_zip_of_files(files):
133
+ """Combine multiple local files into a single .zip for user to download."""
134
+ zip_name = "Arxiv-Paper-Search-QA-RAG-Streamlit-Gradio-AP.zip"
135
+ with zipfile.ZipFile(zip_name, 'w') as zipf:
136
+ for file in files:
137
+ zipf.write(file)
138
+ return zip_name
139
+
140
+ @st.cache_resource
141
+ def get_zip_download_link(zip_file):
142
+ """Return an <a> link to download the given zip_file (base64-encoded)."""
143
+ with open(zip_file, 'rb') as f:
144
+ data = f.read()
145
+ b64 = base64.b64encode(data).decode()
146
+ return f'<a href="data:application/zip;base64,{b64}" download="{zip_file}">Download All</a>'
147
+
148
+ def get_table_download_link(file_path):
149
+ """
150
+ Creates a download link for a single file from your snippet.
151
+ Encodes it as base64 data.
152
+ """
153
+ try:
154
+ with open(file_path, 'r', encoding='utf-8') as file:
155
+ data = file.read()
156
+ b64 = base64.b64encode(data.encode()).decode()
157
+ file_name = os.path.basename(file_path)
158
+ ext = os.path.splitext(file_name)[1]
159
+ mime_map = {
160
+ '.txt': 'text/plain',
161
+ '.py': 'text/plain',
162
+ '.xlsx': 'text/plain',
163
+ '.csv': 'text/plain',
164
+ '.htm': 'text/html',
165
+ '.md': 'text/markdown',
166
+ '.wav': 'audio/wav'
167
+ }
168
+ mime_type = mime_map.get(ext, 'application/octet-stream')
169
+ return f'<a href="data:{mime_type};base64,{b64}" target="_blank" download="{file_name}">{file_name}</a>'
170
+ except:
171
+ return ''
172
+
173
+ def get_file_size(file_path):
174
+ """Get file size in bytes."""
175
+ return os.path.getsize(file_path)
176
+
177
+ def FileSidebar():
178
+ """
179
+ Renders .md files in the sidebar with open/view/run/delete logic.
180
+ """
181
+ all_files = glob.glob("*.md")
182
+ # If you want to filter out short-named or special files:
183
+ all_files = [f for f in all_files if len(os.path.splitext(f)[0]) >= 5]
184
+ all_files.sort(key=lambda x: (os.path.splitext(x)[1], x), reverse=True)
185
+
186
+ # Buttons for "Delete All" and "Download"
187
+ Files1, Files2 = st.sidebar.columns(2)
188
+ with Files1:
189
+ if st.button("🗑 Delete All"):
190
+ for file in all_files:
191
+ os.remove(file)
192
+ st.rerun()
193
+ with Files2:
194
+ if st.button("⬇️ Download"):
195
+ zip_file = create_zip_of_files(all_files)
196
+ st.sidebar.markdown(get_zip_download_link(zip_file), unsafe_allow_html=True)
197
+
198
+ file_contents = ''
199
+ file_name = ''
200
+ next_action = ''
201
+
202
+ # Each file row
203
+ for file in all_files:
204
+ col1, col2, col3, col4, col5 = st.sidebar.columns([1,6,1,1,1])
205
+ with col1:
206
+ if st.button("🌐", key="md_"+file):
207
+ file_contents = load_file(file)
208
+ file_name = file
209
+ next_action = 'md'
210
+ st.session_state['next_action'] = next_action
211
+ with col2:
212
+ st.markdown(get_table_download_link(file), unsafe_allow_html=True)
213
+ with col3:
214
+ if st.button("📂", key="open_"+file):
215
+ file_contents = load_file(file)
216
+ file_name = file
217
+ next_action = 'open'
218
+ st.session_state['lastfilename'] = file
219
+ st.session_state['filename'] = file
220
+ st.session_state['filetext'] = file_contents
221
+ st.session_state['next_action'] = next_action
222
+ with col4:
223
+ if st.button("▶️", key="read_"+file):
224
+ file_contents = load_file(file)
225
+ file_name = file
226
+ next_action = 'search'
227
+ st.session_state['next_action'] = next_action
228
+ with col5:
229
+ if st.button("🗑", key="delete_"+file):
230
+ os.remove(file)
231
+ st.rerun()
232
+
233
+ if file_contents:
234
+ if next_action == 'open':
235
+ open1, open2 = st.columns([0.8, 0.2])
236
+ with open1:
237
+ file_name_input = st.text_input('File Name:', file_name, key='file_name_input')
238
+ file_content_area = st.text_area('File Contents:', file_contents, height=300, key='file_content_area')
239
+
240
+ if st.button('💾 Save File'):
241
+ with open(file_name_input, 'w', encoding='utf-8') as f:
242
+ f.write(file_content_area)
243
+ st.markdown(f'Saved {file_name_input} successfully.')
244
+
245
+ elif next_action == 'search':
246
+ file_content_area = st.text_area("File Contents:", file_contents, height=500)
247
+ user_prompt = PromptPrefix2 + file_contents
248
+ st.markdown(user_prompt)
249
+ if st.button('🔍Re-Code'):
250
+ search_arxiv(file_contents)
251
+
252
+ elif next_action == 'md':
253
+ st.markdown(file_contents)
254
+ SpeechSynthesis(file_contents)
255
+ if st.button("🔍Run"):
256
+ st.write("Running GPT logic placeholder...")
257
+
258
+ # =====================================================================================
259
+ # 4) SCORING / GLOSSARIES
260
+ # =====================================================================================
261
+ score_dir = "scores"
262
+ os.makedirs(score_dir, exist_ok=True)
263
+
264
+ def generate_key(label, header, idx):
265
+ return f"{header}_{label}_{idx}_key"
266
+
267
+ def update_score(key, increment=1):
268
+ """Increment the 'score' for a glossary item in JSON storage."""
269
+ score_file = os.path.join(score_dir, f"{key}.json")
270
+ if os.path.exists(score_file):
271
+ with open(score_file, "r") as file:
272
+ score_data = json.load(file)
273
+ else:
274
+ score_data = {"clicks": 0, "score": 0}
275
+ score_data["clicks"] += increment
276
+ score_data["score"] += increment
277
+ with open(score_file, "w") as file:
278
+ json.dump(score_data, file)
279
+ return score_data["score"]
280
+
281
+ def load_score(key):
282
+ """Load the stored score from .json if it exists, else 0."""
283
+ file_path = os.path.join(score_dir, f"{key}.json")
284
+ if os.path.exists(file_path):
285
+ with open(file_path, "r") as file:
286
+ score_data = json.load(file)
287
+ return score_data["score"]
288
+ return 0
289
+
290
+ def display_buttons_with_scores(num_columns_text):
291
+ """
292
+ Show glossary items as clickable buttons, each increments a 'score'.
293
+ """
294
+ game_emojis = {
295
+ "Dungeons and Dragons": "🐉",
296
+ "Call of Cthulhu": "🐙",
297
+ "GURPS": "🎲",
298
+ "Pathfinder": "🗺️",
299
+ "Kindred of the East": "🌅",
300
+ "Changeling": "🍃",
301
+ }
302
+ topic_emojis = {
303
+ "Core Rulebooks": "📚",
304
+ "Maps & Settings": "🗺️",
305
+ "Game Mechanics & Tools": "⚙️",
306
+ "Monsters & Adversaries": "👹",
307
+ "Campaigns & Adventures": "📜",
308
+ "Creatives & Assets": "🎨",
309
+ "Game Master Resources": "🛠️",
310
+ "Lore & Background": "📖",
311
+ "Character Development": "🧍",
312
+ "Homebrew Content": "🔧",
313
+ "General Topics": "🌍",
314
+ }
315
+
316
+ for category, games in roleplaying_glossary.items():
317
+ category_emoji = topic_emojis.get(category, "🔍")
318
+ st.markdown(f"## {category_emoji} {category}")
319
+ for game, terms in games.items():
320
+ game_emoji = game_emojis.get(game, "🎮")
321
+ for term in terms:
322
+ key = f"{category}_{game}_{term}".replace(' ', '_').lower()
323
+ score_val = load_score(key)
324
+ if st.button(f"{game_emoji} {category} {game} {term} {score_val}", key=key):
325
+ newscore = update_score(key.replace('?', ''))
326
+ st.markdown(f"Scored **{category} - {game} - {term}** -> {newscore}")
327
+
328
+ # =====================================================================================
329
+ # 5) IMAGES & VIDEOS
330
+ # =====================================================================================
331
+ def display_images_and_wikipedia_summaries(num_columns=4):
332
+ """Display .png images in a grid, referencing the name as a 'keyword'."""
333
+ image_files = [f for f in os.listdir('.') if f.endswith('.png')]
334
+ if not image_files:
335
+ st.write("No PNG images found in the current directory.")
336
+ return
337
+
338
+ image_files_sorted = sorted(image_files, key=lambda x: len(x.split('.')[0]))
339
+ cols = st.columns(num_columns)
340
+ col_index = 0
341
+
342
+ for image_file in image_files_sorted:
343
+ with cols[col_index % num_columns]:
344
+ try:
345
+ image = Image.open(image_file)
346
+ st.image(image, use_column_width=True)
347
+ k = image_file.split('.')[0]
348
+ display_glossary_entity(k)
349
+ image_text_input = st.text_input(f"Prompt for {image_file}", key=f"image_prompt_{image_file}")
350
+ if image_text_input:
351
+ response = process_image(image_file, image_text_input)
352
+ st.markdown(response)
353
+ except:
354
+ st.write(f"Could not open {image_file}")
355
+ col_index += 1
356
+
357
+ def display_videos_and_links(num_columns=4):
358
+ """Displays all .mp4/.webm in a grid, plus text input for prompts."""
359
+ video_files = [f for f in os.listdir('.') if f.endswith(('.mp4', '.webm'))]
360
+ if not video_files:
361
+ st.write("No MP4 or WEBM videos found in the current directory.")
362
+ return
363
+
364
+ video_files_sorted = sorted(video_files, key=lambda x: len(x.split('.')[0]))
365
+ cols = st.columns(num_columns)
366
+ col_index = 0
367
+
368
+ for video_file in video_files_sorted:
369
+ with cols[col_index % num_columns]:
370
+ k = video_file.split('.')[0]
371
+ st.video(video_file, format='video/mp4', start_time=0)
372
+ display_glossary_entity(k)
373
+ video_text_input = st.text_input(f"Video Prompt for {video_file}", key=f"video_prompt_{video_file}")
374
+ if video_text_input:
375
+ try:
376
+ seconds_per_frame = 10
377
+ process_video(video_file, seconds_per_frame)
378
+ except ValueError:
379
+ st.error("Invalid input for seconds per frame!")
380
+ col_index += 1
381
+
382
+ # =====================================================================================
383
+ # 6) MERMAID & PARTIAL SUBGRAPH LOGIC
384
+ # =====================================================================================
385
+ def generate_mermaid_html(mermaid_code: str) -> str:
386
+ """Embed mermaid_code in a minimal HTML snippet, centered."""
387
+ return f"""
388
+ <html>
389
+ <head>
390
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
391
+ <style>
392
+ .centered-mermaid {{
393
+ display: flex;
394
+ justify-content: center;
395
+ margin: 20px auto;
396
+ }}
397
+ .mermaid {{
398
+ max-width: 800px;
399
+ }}
400
+ </style>
401
+ </head>
402
+ <body>
403
+ <div class="mermaid centered-mermaid">
404
+ {mermaid_code}
405
+ </div>
406
+ <script>
407
+ mermaid.initialize({{ startOnLoad: true }});
408
+ </script>
409
+ </body>
410
+ </html>
411
+ """
412
+
413
+ def append_model_param(url: str, model_selected: bool) -> str:
414
+ """If user selects 'model=1', we append &model=1 or ?model=1 if not present."""
415
+ if not model_selected:
416
+ return url
417
+ delimiter = "&" if "?" in url else "?"
418
+ return f"{url}{delimiter}model=1"
419
+
420
+ def inject_base_url(url: str) -> str:
421
+ """If link doesn't start with 'http', prepend BASE_URL so it's absolute."""
422
+ if url.startswith("http"):
423
+ return url
424
+ return f"{BASE_URL}{url}"
425
+
426
+ # We'll keep the default mermaid that references /?q=...
427
  DEFAULT_MERMAID = r"""
428
  flowchart LR
429
  U((User 😎)) -- "Talk 🗣️" --> LLM[LLM Agent 🤖\nExtract Info]
 
440
  click KG "/?q=Knowledge%20Graph%20Ontology+GAR+RAG" "Open Knowledge Graph" "_blank"
441
  """
442
 
443
+ # ------------------------------------------------------------------------------------
444
+ # 🍁 Parsing and building partial subgraphs from lines like "A -- Label --> B"
445
+ # We'll do BFS so we can gather multiple downstream levels if we want.
446
+ # ------------------------------------------------------------------------------------
447
  def parse_mermaid_edges(mermaid_text: str):
448
  """
449
  🍿 parse_mermaid_edges:
450
+ - Find lines like: A -- "Label" --> B
451
+ - Return adjacency dict: edges[A] = [(label, B), ...]
452
  """
453
  adjacency = {}
454
+ # e.g. U((User 😎)) -- "Talk 🗣️" --> LLM[LLM Agent 🤖\nExtract Info]
455
  edge_pattern = re.compile(r'(\S+)\s*--\s*"([^"]*)"\s*-->\s*(\S+)')
 
 
456
  for line in mermaid_text.split('\n'):
457
  match = edge_pattern.search(line.strip())
458
  if match:
 
460
  if nodeA not in adjacency:
461
  adjacency[nodeA] = []
462
  adjacency[nodeA].append((label, nodeB))
 
463
  return adjacency
464
 
465
+ def bfs_subgraph(adjacency, start_node, depth=1):
466
  """
467
+ 🍎 bfs_subgraph:
468
+ - Gather edges up to 'depth' levels from start_node
469
+ - If depth=1, only direct edges from node
470
+ - If depth=2, child and grandchild, etc.
471
  """
472
+ from collections import deque
473
+ visited = set()
474
+ queue = deque([(start_node, 0)])
475
+ edges = []
 
 
 
 
476
 
477
+ while queue:
478
+ current, lvl = queue.popleft()
479
+ if current in visited:
480
+ continue
481
+ visited.add(current)
482
+
483
+ if current in adjacency and lvl < depth:
484
+ for (label, child) in adjacency[current]:
485
+ edges.append((current, label, child))
486
+ queue.append((child, lvl + 1))
487
+
488
+ return edges
489
 
490
  def create_subgraph_mermaid(sub_edges, start_node):
491
  """
492
  🍄 create_subgraph_mermaid:
493
+ - build a smaller flowchart snippet with edges from BFS
 
494
  """
 
495
  sub_mermaid = "flowchart LR\n"
496
+ sub_mermaid += f" %% SearchResult Subgraph starting at {start_node}\n"
 
 
497
  for (A, label, B) in sub_edges:
 
 
498
  sub_mermaid += f' {A} -- "{label}" --> {B}\n'
499
+ sub_mermaid += " %% End of partial subgraph\n"
 
 
500
  return sub_mermaid
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
 
503
+ # =====================================================================================
504
+ # 7) MAIN APP
505
+ # =====================================================================================
506
  def main():
507
+ st.set_page_config(page_title="Mermaid + BFS Subgraph + Full Logic", layout="wide")
508
+
509
+ # 1) Query param parsing
510
+ query_params = st.query_params
511
+ query_list = (query_params.get('q') or query_params.get('query') or [''])
512
+ q_or_query = query_list[0].strip() if len(query_list) > 0 else ""
513
 
514
+ # If 'action' param is present
515
+ if 'action' in query_params:
516
+ action_list = query_params['action']
517
+ if action_list:
518
+ action = action_list[0]
519
+ if action == 'show_message':
520
+ st.success("Showing a message because 'action=show_message' was found in the URL.")
521
+ elif action == 'clear':
522
+ clear_query_params()
523
 
524
+ # If there's a 'query=' param, display content or image
525
+ if 'query' in query_params:
526
+ query_val = query_params['query'][0]
527
+ display_content_or_image(query_val)
528
 
529
+ # 2) Let user pick ?model=1
530
+ st.sidebar.write("## Diagram Link Settings")
531
+ model_selected = st.sidebar.checkbox("Append ?model=1 to each link?")
532
+
533
+ # 3) We'll parse adjacency from DEFAULT_MERMAID, then do the injection for base URL
534
+ # and possible model param. We'll store the final mermaid code in session.
535
+ lines = DEFAULT_MERMAID.strip().split("\n")
536
+ new_lines = []
537
+ for line in lines:
538
+ if "click " in line and '"/?' in line:
539
+ # try to parse out the URL
540
+ parts = re.split(r'click\s+\S+\s+"([^"]+)"\s+"([^"]+)"\s+"([^"]+)"', line)
541
+ # For example: parts might be [prefix, '/?q=User%20😎', 'Open User 😎', '_blank', remainder?]
542
+ if len(parts) == 5:
543
+ # Reassemble with base URL + optional model param
544
+ old_url = parts[1]
545
+ tooltip = parts[2]
546
+ target = parts[3]
547
+ # 1) base
548
+ new_url = inject_base_url(old_url)
549
+ # 2) model param
550
+ new_url = append_model_param(new_url, model_selected)
551
+
552
+ new_line = f"{parts[0]}\"{new_url}\" \"{tooltip}\" \"{target}\"{parts[4]}"
553
+ new_lines.append(new_line)
554
+ else:
555
+ new_lines.append(line)
556
+ else:
557
+ new_lines.append(line)
558
+
559
+ final_mermaid = "\n".join(new_lines)
560
+ adjacency = parse_mermaid_edges(final_mermaid)
561
+
562
+ # 4) If user clicked a shape -> we show a partial subgraph as "SearchResult"
563
+ # We'll do BFS with depth=1 or 2 for demonstration:
564
+ partial_subgraph_html = ""
565
+ if q_or_query:
566
+ st.info(f"process_text called with: {PromptPrefix}{q_or_query}")
567
+ # Attempt to find a node whose ID or label includes q_or_query:
568
+ # This may require advanced logic if your IDs differ from labels.
569
+ # We'll do a naive approach: if q_or_query is substring of adjacency keys.
570
  possible_keys = []
571
  for nodeKey in adjacency.keys():
572
+ # e.g. nodeKey might be: 'LLM[LLM Agent 🤖\nExtract Info]'
573
+ # we'll check if q_or_query is substring ignoring spaces
574
+ simplified_key = nodeKey.replace("\\n", " ").replace("[", "").replace("]", "").lower()
575
+ simplified_query = q_or_query.lower().replace("%20", " ")
576
+ if simplified_query in simplified_key:
577
  possible_keys.append(nodeKey)
578
+
579
  if possible_keys:
580
  chosen_node = possible_keys[0]
581
+ st.info(f"Chosen node for subgraph: {chosen_node}")
582
+ sub_edges = bfs_subgraph(adjacency, chosen_node, depth=1)
583
  if sub_edges:
584
  sub_mermaid = create_subgraph_mermaid(sub_edges, chosen_node)
585
+ partial_subgraph_html = generate_mermaid_html(sub_mermaid)
 
 
 
 
 
 
586
  else:
587
+ st.warning("No adjacency node matched the query param's text. Subgraph is empty.")
588
+
589
+ # 5) Show partial subgraph top-center if we have any
590
+ if partial_subgraph_html:
591
+ st.subheader("SearchResult Subgraph")
592
+ components.html(partial_subgraph_html, height=300, scrolling=False)
593
+
594
+ # 6) Render the top-centered *full* diagram
595
+ st.title("Full Mermaid Diagram (with Base URL + model=1 logic)")
596
+ diagram_html = generate_mermaid_html(final_mermaid)
597
+ components.html(diagram_html, height=400, scrolling=True)
598
+
599
+ # 7) Editor columns: Markdown & Mermaid
600
+ left_col, right_col = st.columns(2)
601
+
602
+ with left_col:
603
+ st.subheader("Markdown Side 📝")
604
+ if "markdown_text" not in st.session_state:
605
+ st.session_state["markdown_text"] = "## Hello!\nYou can type some *Markdown* here.\n"
606
+ markdown_text = st.text_area(
607
+ "Edit Markdown:",
608
+ value=st.session_state["markdown_text"],
609
+ height=300
610
+ )
611
+ st.session_state["markdown_text"] = markdown_text
612
+
613
+ # Buttons
614
+ colA, colB = st.columns(2)
615
+ with colA:
616
+ if st.button("🔄 Refresh Markdown"):
617
+ st.write("**Markdown** content refreshed! 🍿")
618
+ with colB:
619
+ if st.button("❌ Clear Markdown"):
620
+ st.session_state["markdown_text"] = ""
621
+ st.rerun()
622
+
623
+ st.markdown("---")
624
+ st.markdown("**Preview:**")
625
+ st.markdown(markdown_text)
626
+
627
+ with right_col:
628
+ st.subheader("Mermaid Side 🧜‍♂️")
629
+ if "current_mermaid" not in st.session_state:
630
+ st.session_state["current_mermaid"] = final_mermaid
631
+
632
+ # Let user see the final code we built
633
+ mermaid_input = st.text_area(
634
+ "Edit Mermaid Code:",
635
+ value=st.session_state["current_mermaid"],
636
+ height=300
637
+ )
638
+ colC, colD = st.columns(2)
639
+ with colC:
640
+ if st.button("🎨 Refresh Diagram"):
641
+ st.session_state["current_mermaid"] = mermaid_input
642
+ st.write("**Mermaid** diagram refreshed! 🌈")
643
+ st.rerun()
644
+ with colD:
645
+ if st.button("❌ Clear Mermaid"):
646
+ st.session_state["current_mermaid"] = ""
647
+ st.rerun()
648
+
649
+ st.markdown("---")
650
+ st.markdown("**Mermaid Source:**")
651
+ st.code(mermaid_input, language="python", line_numbers=True)
652
+
653
+ # 8) Show the galleries
654
+ st.markdown("---")
655
+ st.header("Media Galleries")
656
+ num_columns_images = st.slider("Choose Number of Image Columns", 1, 15, 5, key="num_columns_images")
657
+ display_images_and_wikipedia_summaries(num_columns_images)
658
+
659
+ num_columns_video = st.slider("Choose Number of Video Columns", 1, 15, 5, key="num_columns_video")
660
+ display_videos_and_links(num_columns_video)
661
+
662
+ # 9) Possibly show extended text interface
663
+ showExtendedTextInterface = False
664
+ if showExtendedTextInterface:
665
+ # e.g. display_glossary_grid(roleplaying_glossary)
666
+ # num_columns_text = st.slider("Choose Number of Text Columns", 1, 15, 4)
667
+ # display_buttons_with_scores(num_columns_text)
668
+ pass
669
+
670
+ # 10) Render the file sidebar
671
+ FileSidebar()
672
+
673
+ # 11) Random title at bottom
674
+ titles = [
675
+ "🧠🎭 Semantic Symphonies & Episodic Encores",
676
+ "🌌🎼 AI Rhythms of Memory Lane",
677
+ "🎭🎉 Cognitive Crescendos & Neural Harmonies",
678
+ "🧠🎺 Mnemonic Melodies & Synaptic Grooves",
679
+ "🎼🎸 Straight Outta Cognition",
680
+ "🥁🎻 Jazzy Jambalaya of AI Memories",
681
+ "🏰 Semantic Soul & Episodic Essence",
682
+ "🥁🎻 The Music Of AI's Mind"
683
+ ]
684
+ st.markdown(f"**{random.choice(titles)}**")
685
 
686
 
687
  if __name__ == "__main__":