fewsvisualiser 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. fewsvisualiser-1.0.1/.github/workflows/python-publish.yml +40 -0
  2. fewsvisualiser-1.0.1/.gitignore +19 -0
  3. fewsvisualiser-1.0.1/.vscode/settings.json +10 -0
  4. fewsvisualiser-1.0.1/ConfigVisDatamodel.png +0 -0
  5. fewsvisualiser-1.0.1/PKG-INFO +54 -0
  6. fewsvisualiser-1.0.1/README.md +41 -0
  7. fewsvisualiser-1.0.1/app/components/__init__.py +10 -0
  8. fewsvisualiser-1.0.1/app/components/sidebar_chat.py +327 -0
  9. fewsvisualiser-1.0.1/app/components/tab_explore.py +91 -0
  10. fewsvisualiser-1.0.1/app/components/tab_reports.py +284 -0
  11. fewsvisualiser-1.0.1/app/components/tools_interface.py +137 -0
  12. fewsvisualiser-1.0.1/app/streamlit_app.py +56 -0
  13. fewsvisualiser-1.0.1/app/utils.py +26 -0
  14. fewsvisualiser-1.0.1/app_scope.md +141 -0
  15. fewsvisualiser-1.0.1/configvis.ipynb +200 -0
  16. fewsvisualiser-1.0.1/db/.gitkeep +1 -0
  17. fewsvisualiser-1.0.1/details.md +544 -0
  18. fewsvisualiser-1.0.1/doc/FEWS visualizer.pptx +0 -0
  19. fewsvisualiser-1.0.1/doc/aantekeningen.docx +0 -0
  20. fewsvisualiser-1.0.1/doc/queries.txt +41 -0
  21. fewsvisualiser-1.0.1/docker/Dockerfile +32 -0
  22. fewsvisualiser-1.0.1/env/.env.example +26 -0
  23. fewsvisualiser-1.0.1/fews-networkvisualisation.code-workspace +8 -0
  24. fewsvisualiser-1.0.1/fewsvisualiser/__init__.py +11 -0
  25. fewsvisualiser-1.0.1/fewsvisualiser/__main__.py +8 -0
  26. fewsvisualiser-1.0.1/fewsvisualiser/analyse.py +61 -0
  27. fewsvisualiser-1.0.1/fewsvisualiser/core/__init__.py +0 -0
  28. fewsvisualiser-1.0.1/fewsvisualiser/core/dbconfigvis.py +401 -0
  29. fewsvisualiser-1.0.1/fewsvisualiser/core/dblocset.py +79 -0
  30. fewsvisualiser-1.0.1/fewsvisualiser/core/fewsparsers.py +543 -0
  31. fewsvisualiser-1.0.1/fewsvisualiser/core/helpers.py +100 -0
  32. fewsvisualiser-1.0.1/fewsvisualiser/core/locsetparser.py +76 -0
  33. fewsvisualiser-1.0.1/fewsvisualiser/explore.py +123 -0
  34. fewsvisualiser-1.0.1/fewsvisualiser/locset_analysis.py +43 -0
  35. fewsvisualiser-1.0.1/notebooks/analyze_endpoints.py +240 -0
  36. fewsvisualiser-1.0.1/notebooks/talkwithfews.ipynb +398 -0
  37. fewsvisualiser-1.0.1/notebooks/validate_tools.ipynb +304 -0
  38. fewsvisualiser-1.0.1/pixi.lock +10640 -0
  39. fewsvisualiser-1.0.1/pixi.toml +33 -0
  40. fewsvisualiser-1.0.1/pyproject.toml +21 -0
  41. fewsvisualiser-1.0.1/scope.md +75 -0
  42. fewsvisualiser-1.0.1/scripts/README.md +6 -0
  43. fewsvisualiser-1.0.1/scripts/run_visualiser.ipynb +140 -0
  44. fewsvisualiser-1.0.1/scripts/run_visualiser.py +23 -0
  45. fewsvisualiser-1.0.1/talkwithfews/README.md +59 -0
  46. fewsvisualiser-1.0.1/talkwithfews/__init__.py +8 -0
  47. fewsvisualiser-1.0.1/talkwithfews/agent.py +244 -0
  48. fewsvisualiser-1.0.1/talkwithfews/logging_utils.py +143 -0
  49. fewsvisualiser-1.0.1/talkwithfews/models.py +492 -0
  50. fewsvisualiser-1.0.1/talkwithfews/schema_utils.py +337 -0
  51. fewsvisualiser-1.0.1/talkwithfews/tools.py +1425 -0
  52. fewsvisualiser-1.0.1/talkwithfews/utils.py +138 -0
  53. fewsvisualiser-1.0.1/tests/__init__.py +0 -0
  54. fewsvisualiser-1.0.1/tests/files/.gitignore +1 -0
  55. fewsvisualiser-1.0.1/tests/files/.gitkeep +1 -0
  56. fewsvisualiser-1.0.1/tests/test_core.py +26 -0
@@ -0,0 +1,40 @@
1
+ # This workflow will upload a Python Package using Twine when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ # This workflow uses actions that are not certified by GitHub.
5
+ # They are provided by a third-party and are governed by
6
+ # separate terms of service, privacy policy, and support
7
+ # documentation.
8
+
9
+ name: Upload Python Package
10
+
11
+ on:
12
+ release:
13
+ types: [published]
14
+
15
+ permissions:
16
+ contents: read
17
+ id-token: write
18
+
19
+ jobs:
20
+ deploy:
21
+
22
+ runs-on: ubuntu-latest
23
+ # Specifying a GitHub environment is optional, but strongly encouraged
24
+ environment: release
25
+ steps:
26
+ - uses: actions/checkout@v6
27
+ - name: Set up Python
28
+ uses: actions/setup-python@v6
29
+ with:
30
+ python-version: '3.x'
31
+ - name: Install dependencies
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ pip install build
35
+ - name: Build package
36
+ run: python -m build
37
+ - name: Publish package
38
+ uses: pypa/gh-action-pypi-publish@release/v1
39
+ with:
40
+ packages-dir: dist
@@ -0,0 +1,19 @@
1
+ /db/*.db3
2
+ __pycache__
3
+ aantekeningen.doc
4
+ test*.ipynb
5
+ /.vscode/launch.json
6
+ /config
7
+ .pixi
8
+ .DS_Store
9
+ db/*.log
10
+ notebooks/*
11
+ !notebooks/*.py
12
+ !notebooks/talkwithfews.ipynb
13
+ !notebooks/validate_tools.ipynb
14
+ env/.env
15
+ logs/fews_agent.log
16
+ doc/TalkWithFEWS-7april-GO-FEWS.key
17
+ hidden_*
18
+ doc/TalkWithFEWS-7april-GO-FEWS.pdf
19
+ dist
@@ -0,0 +1,10 @@
1
+ {
2
+ "python.defaultInterpreterPath": ".pixi/envs/default3/python.exe",
3
+ "python.testing.pytestArgs": [
4
+ "tests"
5
+ ],
6
+ "python.testing.unittestEnabled": false,
7
+ "python.testing.pytestEnabled": true,
8
+ "search.useGlobalIgnoreFiles": true,
9
+ "python-envs.defaultEnvManager": "ms-python.python:system"
10
+ }
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.5
2
+ Name: fewsvisualiser
3
+ Version: 1.0.1
4
+ Summary: FEWSVisualiser: Insights for optimized Delft-FEWS configurations.
5
+ Author-email: Job Verkaik <verkaik@hkv.nl>
6
+ Keywords: configuration,declarative,delft-fews,insight
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: License :: OSI Approved :: BSD License
10
+ Requires-Python: >=3.8
11
+ Requires-Dist: fire
12
+ Description-Content-Type: text/markdown
13
+
14
+ # FEWSVisualiser: Insights for optimized Delft-FEWS configurations
15
+
16
+ A differerent approach to the analysis and visualisation of Delft-FEWS configuration
17
+
18
+ This project is a successor of [FEWS_Netwerkvisualisation](https://github.com/HKV-products-services/FEWS_Netwerkvisualisation)
19
+
20
+ HKV project
21
+ RO1122.10.00 Herstructurering analyse netwerkvisualisatie
22
+
23
+ ## Talk with fews
24
+
25
+ > [!CAUTION]
26
+ > De eerste ontiwikkelingen van `talk with FEWS` waren in deze repo onder `/talkwithfews`.
27
+ > Dit is echter losgetrokken, de actuele repo is: ![hier](https://github.com/HKV-products-services/talk-with-fews) te vinden.
28
+ > Actiepunt: later volledig lostrekken.
29
+
30
+ ## aantekeningen
31
+
32
+ ![Config Vis Datamodel](ConfigVisDatamodel.png)
33
+
34
+ 2024-02-11
35
+
36
+ Zie explore. Waarom zijn er 5 call stacks met wf_DebietBerekening?
37
+
38
+ 2024-02-12
39
+
40
+ Die call stacks zijn wsl goed. Elke module binnen een wf krijgt namelijk een eigen call stack en dat is terecht. Alleen dit deel moet even goed anders: de volgorde van het aanroepen van modules binnen en wf is van belang wanneer er temporaries worden doorgegeven naar een volgende module. Dit is een case die nu nog niet ondersteund wordt.
41
+
42
+ De betere manier is om in de call stacks per wf vanuit de descriptor de volgorde van modins call vast te leggen. Temporaries kunnen dan op een andere manier door gegeven worden en hoeven niet de database in.
43
+
44
+ ## How to run?
45
+
46
+ ```bash
47
+ pixi install
48
+ ```
49
+
50
+ ```bash
51
+ pixi run analyse_config {path_to_config} {path_to_db}
52
+ # e.g:
53
+ pixi run analyse_config "..\FEWS Digiflow\Config" "db\digiflow.db3"
54
+ ```
@@ -0,0 +1,41 @@
1
+ # FEWSVisualiser: Insights for optimized Delft-FEWS configurations
2
+
3
+ A differerent approach to the analysis and visualisation of Delft-FEWS configuration
4
+
5
+ This project is a successor of [FEWS_Netwerkvisualisation](https://github.com/HKV-products-services/FEWS_Netwerkvisualisation)
6
+
7
+ HKV project
8
+ RO1122.10.00 Herstructurering analyse netwerkvisualisatie
9
+
10
+ ## Talk with fews
11
+
12
+ > [!CAUTION]
13
+ > De eerste ontiwikkelingen van `talk with FEWS` waren in deze repo onder `/talkwithfews`.
14
+ > Dit is echter losgetrokken, de actuele repo is: ![hier](https://github.com/HKV-products-services/talk-with-fews) te vinden.
15
+ > Actiepunt: later volledig lostrekken.
16
+
17
+ ## aantekeningen
18
+
19
+ ![Config Vis Datamodel](ConfigVisDatamodel.png)
20
+
21
+ 2024-02-11
22
+
23
+ Zie explore. Waarom zijn er 5 call stacks met wf_DebietBerekening?
24
+
25
+ 2024-02-12
26
+
27
+ Die call stacks zijn wsl goed. Elke module binnen een wf krijgt namelijk een eigen call stack en dat is terecht. Alleen dit deel moet even goed anders: de volgorde van het aanroepen van modules binnen en wf is van belang wanneer er temporaries worden doorgegeven naar een volgende module. Dit is een case die nu nog niet ondersteund wordt.
28
+
29
+ De betere manier is om in de call stacks per wf vanuit de descriptor de volgorde van modins call vast te leggen. Temporaries kunnen dan op een andere manier door gegeven worden en hoeven niet de database in.
30
+
31
+ ## How to run?
32
+
33
+ ```bash
34
+ pixi install
35
+ ```
36
+
37
+ ```bash
38
+ pixi run analyse_config {path_to_config} {path_to_db}
39
+ # e.g:
40
+ pixi run analyse_config "..\FEWS Digiflow\Config" "db\digiflow.db3"
41
+ ```
@@ -0,0 +1,10 @@
1
+ from .sidebar_chat import init_chat_state, render_chat_sidebar
2
+ from .tab_explore import render_explore_tab
3
+ from .tab_reports import render_reports_tab
4
+
5
+ __all__ = [
6
+ 'init_chat_state',
7
+ 'render_chat_sidebar',
8
+ 'render_explore_tab',
9
+ 'render_reports_tab'
10
+ ]
@@ -0,0 +1,327 @@
1
+ import sys
2
+ from pathlib import Path
3
+ # Add parent directory to path for imports
4
+ sys.path.append(str(Path(__file__).parent.parent.parent))
5
+
6
+ import streamlit as st
7
+ from streamlit_markdown import st_markdown
8
+ from talkwithfews.agent import FEWSConfigAgent
9
+ import asyncio
10
+ from queue import Queue, Empty
11
+ import threading
12
+ import time
13
+ import logging
14
+
15
+ # Set up logging
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class AgentManager:
20
+ """Manages the FEWS agent in a separate thread"""
21
+ def __init__(self, config):
22
+ self.config = config
23
+ self.agent = None
24
+ self.response_queue = Queue()
25
+ self.command_queue = Queue()
26
+ self.thread = None
27
+ self.ready = threading.Event()
28
+ self.processing = False
29
+ self.error = None
30
+
31
+ def initialize(self):
32
+ """Initialize the agent in a dedicated thread"""
33
+ def agent_thread():
34
+ try:
35
+ # Create and initialize agent
36
+ self.agent = FEWSConfigAgent(
37
+ db_path=self.config["db_path"],
38
+ log_dir=self.config["log_dir"]
39
+ )
40
+ self.agent.connect()
41
+
42
+ # Set up event loop
43
+ loop = asyncio.new_event_loop()
44
+ asyncio.set_event_loop(loop)
45
+
46
+ # Signal ready
47
+ self.ready.set()
48
+
49
+ while True:
50
+ try:
51
+ command = self.command_queue.get(timeout=1)
52
+ if command is None: # Shutdown signal
53
+ break
54
+
55
+ self.processing = True
56
+ self.error = None
57
+
58
+ try:
59
+ response = loop.run_until_complete(asyncio.wait_for(
60
+ self.agent.ask(command),
61
+ timeout=300
62
+ ))
63
+ self.response_queue.put(response)
64
+ except asyncio.TimeoutError:
65
+ self.error = "Request timed out after 5 minutes"
66
+ self.response_queue.put(self.error)
67
+ except Exception as e:
68
+ self.error = str(e)
69
+ self.response_queue.put(f"Error: {str(e)}")
70
+ finally:
71
+ self.processing = False
72
+
73
+ except Empty:
74
+ continue
75
+ except Exception as e:
76
+ self.error = str(e)
77
+ self.response_queue.put(f"Error: {str(e)}")
78
+ self.processing = False
79
+
80
+ except Exception as e:
81
+ self.error = str(e)
82
+ self.response_queue.put(f"Error: {str(e)}")
83
+ finally:
84
+ try:
85
+ loop.close()
86
+ except:
87
+ pass
88
+
89
+ self.thread = threading.Thread(target=agent_thread, daemon=True)
90
+ self.thread.start()
91
+ if not self.ready.wait(timeout=10):
92
+ raise Exception("Agent initialization timed out")
93
+
94
+ def ask(self, prompt):
95
+ """Send a question to the agent thread"""
96
+ if not self.thread or not self.thread.is_alive():
97
+ raise Exception("Agent thread is not running")
98
+
99
+ self.command_queue.put(prompt)
100
+ start_time = time.time()
101
+
102
+ while time.time() - start_time < 300: # 5 minutes timeout
103
+ try:
104
+ # Wait for response with a short timeout
105
+ response = self.response_queue.get(timeout=1)
106
+ if isinstance(response, str) and response.startswith("Error:"):
107
+ raise Exception(response)
108
+ return response
109
+ except Empty:
110
+ # If we're still processing, keep waiting
111
+ if self.processing:
112
+ continue
113
+ # If we have an error, raise it
114
+ if self.error:
115
+ raise Exception(self.error)
116
+ # Otherwise, keep waiting
117
+ continue
118
+
119
+ raise Exception("Timeout waiting for response")
120
+
121
+ def close(self):
122
+ """Close the agent and cleanup"""
123
+ if self.thread and self.thread.is_alive():
124
+ self.command_queue.put(None)
125
+ self.thread.join(timeout=5)
126
+ self.agent = None
127
+ self.thread = None
128
+ self.ready.clear()
129
+ self.processing = False
130
+ self.error = None
131
+
132
+ def init_chat_state():
133
+ """Initialize chat state and agent if not exists"""
134
+ if "messages" not in st.session_state:
135
+ st.session_state.messages = []
136
+ if "agent_manager" not in st.session_state:
137
+ st.session_state.agent_manager = None
138
+ if "selected_data" not in st.session_state:
139
+ st.session_state.selected_data = None
140
+
141
+ def format_agent_response(response):
142
+ """Format the agent response for display"""
143
+ if hasattr(response, 'data'):
144
+ content = {
145
+ "text": response.data.markdown_text or "",
146
+ "table": response.data.markdown_table or "",
147
+ "diagram": response.data.mermaid_diagram or ""
148
+ }
149
+ else:
150
+ content = {"text": str(response), "table": "", "diagram": ""}
151
+ return content
152
+
153
+ def add_to_chat(data: dict):
154
+ """
155
+ Add FEWS object data to the chat as a question and get agent response.
156
+
157
+ Args:
158
+ data: Dictionary containing FEWS object information with at least a 'type' field
159
+ Supported types:
160
+ - workflow: Contains Workflow ID, Beschrijving, Module Config IDs
161
+ - timeseries: Contains ID, Module, Parameter info for timeseries
162
+ Future support planned for:
163
+ - transformation: Contains transformation details
164
+ """
165
+ if "messages" not in st.session_state:
166
+ st.session_state.messages = []
167
+
168
+ # Format question based on object type
169
+ if data.get("type") == "timeseries":
170
+ # For timeseries sets
171
+ ts_id = data.get("ID")
172
+ module = data.get("Module", "")
173
+ parameter = data.get("Parameter", "")
174
+ question = (
175
+ f"Kun je me meer vertellen over de tijdreeks met ID {ts_id}? "
176
+ f"Deze tijdreeks is van parameter '{parameter}' in module '{module}'. "
177
+ f"Wat zijn de producerende transformaties en waar wordt deze tijdreeks voor gebruikt?"
178
+ f"Met andere woorden, waarom is dit een dood punt in de configuratie?"
179
+ )
180
+ elif data.get("type") == "workflow":
181
+ # For workflows (renamed from the default case)
182
+ workflow_id = data.get("Workflow ID")
183
+ if not workflow_id:
184
+ st.error("Geen workflow ID gevonden in de data")
185
+ return
186
+
187
+ # Format the workflow data into a clear question
188
+ question = (
189
+ f"Kun je me meer vertellen over de workflow met ID {workflow_id}? "
190
+ f"Deze workflow heeft als beschrijving: '{data.get('Beschrijving', 'Geen beschrijving')}' "
191
+ f"en gebruikt de volgende Module Config IDs: {data.get('Module Config IDs', [])}"
192
+ )
193
+ else:
194
+ st.error(f"Niet-ondersteund object type: {data.get('type', 'onbekend')}")
195
+ return
196
+
197
+ # Add the question to the chat history
198
+ st.session_state.messages.append({
199
+ "role": "user",
200
+ "content": question
201
+ })
202
+
203
+ # Store the question in session state to process after rerun
204
+ if "pending_question" not in st.session_state:
205
+ st.session_state.pending_question = None
206
+ st.session_state.pending_question = question
207
+
208
+ # Trigger a rerun to update the UI with the question
209
+ st.rerun()
210
+
211
+ def show_table_dialog(table_content: str, title: str = "Table Details"):
212
+ """Display a table in a popover."""
213
+ with st.popover(title):
214
+ st.markdown(table_content)
215
+ if st.button("Close", use_container_width=True):
216
+ st.rerun()
217
+
218
+ def format_mermaid_diagram(diagram_content: str) -> str:
219
+ """Format the diagram content to ensure proper mermaid syntax."""
220
+ if not diagram_content.startswith("```mermaid"):
221
+ diagram_content = "```mermaid\n" + diagram_content
222
+ if not diagram_content.endswith("```"):
223
+ diagram_content = diagram_content + "\n```"
224
+ return diagram_content
225
+
226
+ def render_chat_sidebar(config=None):
227
+ """Render the chat interface in the sidebar"""
228
+ # Add custom CSS for the diagram container
229
+ st.markdown("""
230
+ <style>
231
+ .diagram-container {
232
+ min-height: 400px;
233
+ max-height: 400px;
234
+ overflow-y: auto;
235
+ border: 1px solid #e0e0e0;
236
+ border-radius: 4px;
237
+ padding: 10px;
238
+ }
239
+ </style>
240
+ """, unsafe_allow_html=True)
241
+
242
+ with st.sidebar:
243
+ st.header("Chat Interface")
244
+
245
+ # Initialize agent if config provided
246
+ if config and st.session_state.agent_manager is None:
247
+ try:
248
+ st.session_state.agent_manager = AgentManager(config)
249
+ st.session_state.agent_manager.initialize()
250
+ except Exception as e:
251
+ st.error(f"Error initializing FEWS agent: {str(e)}")
252
+ return
253
+
254
+ # Display chat messages
255
+ for idx, message in enumerate(st.session_state.messages):
256
+ with st.chat_message(message["role"]):
257
+ content = message["content"]
258
+
259
+ # Handle both string and dictionary content
260
+ if isinstance(content, str):
261
+ st.markdown(content)
262
+ else:
263
+ # Display text
264
+ if content.get("text"):
265
+ st.markdown(content["text"])
266
+
267
+ # Show table and diagram popovers if available
268
+ if content.get("table") or content.get("diagram"):
269
+ cols = st.columns(2)
270
+
271
+ # Table popover in first column if available
272
+ if content.get("table"):
273
+ with cols[0]:
274
+ with st.popover("📊 Table Details"):
275
+ st.markdown(content["table"])
276
+
277
+ # Diagram popover in second column if available
278
+ if content.get("diagram"):
279
+ with cols[1]:
280
+ with st.popover("📈 Diagram Details"):
281
+ # Format and display the mermaid diagram
282
+ formatted_diagram = format_mermaid_diagram(content["diagram"])
283
+ st_markdown(formatted_diagram, height=380)
284
+
285
+ # Process pending question if exists
286
+ if hasattr(st.session_state, 'pending_question') and st.session_state.pending_question:
287
+ question = st.session_state.pending_question
288
+ st.session_state.pending_question = None # Clear the pending question
289
+
290
+ # Get agent response if available
291
+ if st.session_state.agent_manager:
292
+ with st.spinner("Thinking..."):
293
+ try:
294
+ response = st.session_state.agent_manager.ask(question)
295
+ content = format_agent_response(response)
296
+ st.session_state.messages.append({
297
+ "role": "assistant",
298
+ "content": content
299
+ })
300
+ except Exception as e:
301
+ st.error(str(e))
302
+ st.session_state.messages.append({
303
+ "role": "assistant",
304
+ "content": {"text": f"Error: {str(e)}"}
305
+ })
306
+ st.rerun() # Update UI with the response
307
+
308
+ # Chat input
309
+ if prompt := st.chat_input("Ask about FEWS configuration..."):
310
+ # Add user message
311
+ st.session_state.messages.append({
312
+ "role": "user",
313
+ "content": prompt
314
+ })
315
+
316
+ # Store as pending question
317
+ st.session_state.pending_question = prompt
318
+ st.rerun()
319
+
320
+ # Cleanup button
321
+ if st.button("End Session", use_container_width=True):
322
+ if st.session_state.agent_manager:
323
+ st.session_state.agent_manager.close()
324
+ st.session_state.agent_manager = None
325
+ st.session_state.messages = []
326
+ st.session_state.pending_question = None
327
+ st.rerun()
@@ -0,0 +1,91 @@
1
+ import streamlit as st
2
+ import pandas as pd
3
+ from datetime import datetime
4
+
5
+ from .sidebar_chat import add_to_chat
6
+
7
+ def load_workflow_data():
8
+ """Load workflow data using the search_workflows function"""
9
+ try:
10
+ # Check if tools interface is available
11
+ if "tools_interface" not in st.session_state:
12
+ st.error("Database connection not available. Please check your configuration.")
13
+ return pd.DataFrame()
14
+
15
+ # Use the tools interface from session state
16
+ tools = st.session_state.tools_interface
17
+
18
+ # Use '%' as default pattern to get all workflows
19
+ results = tools.search_workflows(name_pattern='%')
20
+
21
+ if results and hasattr(results, 'results'):
22
+ data = []
23
+ for workflow in results.results:
24
+ data.append({
25
+ "ID": workflow.id, # Hidden column
26
+ "Workflow ID": workflow.wf_id,
27
+ "Beschrijving": workflow.desc,
28
+ "Module Config IDs": ', '.join(workflow.module_config_ids) if workflow.module_config_ids else 'Geen'
29
+ })
30
+ return pd.DataFrame(data)
31
+ return pd.DataFrame() # Return empty DataFrame if no results
32
+ except Exception as e:
33
+ st.error(f"Error loading workflows: {str(e)}")
34
+ return pd.DataFrame()
35
+
36
+ def render_explore_tab():
37
+ """Render the explore tab content with workflow data"""
38
+ st.header("Explore Workflows")
39
+
40
+ # Load workflow data
41
+ df = load_workflow_data()
42
+
43
+ if df.empty:
44
+ st.warning("No workflows found or error loading workflows.")
45
+ return
46
+
47
+ # Define which columns to display
48
+ display_columns = ["Workflow ID", "Beschrijving", "Module Config IDs"]
49
+
50
+ # Add search functionality
51
+ search_term = st.text_input("Search workflows", "")
52
+
53
+ # Filter dataframe based on search term
54
+ if search_term:
55
+ # Search in all columns, including hidden ones
56
+ mask = df.astype(str).apply(lambda x: x.str.contains(search_term, case=False)).any(axis=1)
57
+ filtered_df = df[mask]
58
+ else:
59
+ filtered_df = df
60
+
61
+ # Add header row
62
+ with st.container():
63
+ header_cols = st.columns([0.15] + [1] * len(display_columns))
64
+ header_cols[0].markdown("")
65
+ for i, col_name in enumerate(display_columns):
66
+ header_cols[i+1].markdown(f"**{col_name}**")
67
+ st.divider()
68
+
69
+ # Create a container for each row with hover effect
70
+ for idx, row in filtered_df.iterrows():
71
+ with st.container():
72
+ # Create columns only for displayed columns plus the chat button
73
+ cols = st.columns([0.15] + [1] * len(display_columns))
74
+
75
+ # Add to chat button
76
+ if cols[0].button("💬", key=f"btn_{idx}", use_container_width=True):
77
+ # Add the workflow type to the dictionary
78
+ workflow_data = row.to_dict() # This includes all columns, even hidden ones
79
+ workflow_data["type"] = "workflow"
80
+ add_to_chat(workflow_data)
81
+
82
+ # Display only the visible columns
83
+ for i, col_name in enumerate(display_columns, 1):
84
+ cols[i].write(row[col_name])
85
+
86
+ # Add a subtle divider
87
+ st.divider()
88
+
89
+ # Show result count
90
+ st.markdown(f"**{len(filtered_df)} workflows found**" +
91
+ (f" (filtered from {len(df)} total)" if search_term else ""))