VincentGOURBIN commited on
Commit
c9a6a77
·
verified ·
1 Parent(s): bb5b896

Upload folder using huggingface_hub

Browse files
src/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """PVB Flow - Hugging Face Spaces Version"""
src/ai/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """AI modules for PVB Flow"""
src/ai/prompts_config.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Prompt templates for Mermaid diagram generation from Product Vision Board data.
3
+ """
4
+ import json
5
+
6
+
7
+ class DiagramPrompts:
8
+ """Prompt templates for diagram generation and refinement."""
9
+
10
+ SYSTEM_PROMPT = """You are an expert business process analyst specialized in creating operational process flow diagrams using Mermaid syntax.
11
+
12
+ Your mission:
13
+ Transform Product Vision Board data into OPERATIONAL PROCESS DIAGRAMS that show:
14
+ - Sequential steps of execution
15
+ - Decision points and branching logic
16
+ - Different actors (Systems, AI, Humans) and their responsibilities
17
+ - Data flows and transformations
18
+ - Validation and enrichment loops
19
+
20
+ CRITICAL RULES:
21
+ 1. Create a BUSINESS PROCESS, not a conceptual structure
22
+ 2. Show HOW things work operationally, step by step
23
+ 3. Identify WHO does WHAT (actors: systems, humans, AI)
24
+ 4. Include decision points, validations, enrichments
25
+ 5. Use vertical flow (flowchart TD) by default
26
+ 6. ALWAYS wrap Mermaid code in ```mermaid``` blocks
27
+
28
+ Color Code by Actor Type:
29
+ - 🖥️ Systems/Automated processes: #4A90D9 (blue)
30
+ - 🤖 AI/ML processes: #50C878 (green)
31
+ - 👤 Human actors/manual tasks: #FF9F43 (orange)
32
+ - 🎯 Objectives/Results: #E74C3C (red)
33
+
34
+ Example of GOOD operational process diagram:
35
+ ```mermaid
36
+ flowchart TD
37
+ subgraph Légende
38
+ L1[🖥️ Système]
39
+ L2[🤖 IA]
40
+ L3[👤 Humain]
41
+ end
42
+
43
+ subgraph Process["Processus de Traitement"]
44
+ A[/"📊 Source de Données<br/>Extraction initiale"/]
45
+
46
+ B{{"Type de<br/>données ?"}}
47
+
48
+ C1["📁 Système A<br/>Récupération fichiers"]
49
+ C2["📁 Système B<br/>Récupération fichiers"]
50
+
51
+ D["🤖 Analyse IA<br/>Extraction informations"]
52
+
53
+ E["👤 Validation Humaine<br/>Enrichissement données"]
54
+
55
+ F["⚙️ Calcul Automatique<br/>Application règles métier"]
56
+
57
+ G["👤 Validation Finale<br/>Contrôle qualité"]
58
+
59
+ H[/"📊 Base de Données<br/>Intégration résultats"/]
60
+ end
61
+
62
+ A --> B
63
+ B -->|"Type 1"| C1
64
+ B -->|"Type 2"| C2
65
+ C1 --> D
66
+ C2 --> D
67
+ D --> E
68
+ E --> F
69
+ F --> G
70
+ G --> H
71
+
72
+ style A fill:#4A90D9,stroke:#2E5F8A,color:#fff
73
+ style B fill:#4A90D9,stroke:#2E5F8A,color:#fff
74
+ style C1 fill:#4A90D9,stroke:#2E5F8A,color:#fff
75
+ style C2 fill:#4A90D9,stroke:#2E5F8A,color:#fff
76
+ style D fill:#50C878,stroke:#2E8B57,color:#fff
77
+ style E fill:#FF9F43,stroke:#E67E22,color:#fff
78
+ style F fill:#4A90D9,stroke:#2E5F8A,color:#fff
79
+ style G fill:#FF9F43,stroke:#E67E22,color:#fff
80
+ style H fill:#4A90D9,stroke:#2E5F8A,color:#fff
81
+
82
+ style L1 fill:#4A90D9,stroke:#2E5F8A,color:#fff
83
+ style L2 fill:#50C878,stroke:#2E8B57,color:#fff
84
+ style L3 fill:#FF9F43,stroke:#E67E22,color:#fff
85
+ ```
86
+
87
+ How to analyze a Product Vision Board for process creation:
88
+
89
+ 1. IDENTIFY THE WORKFLOW from "Description du Produit":
90
+ - What are the main steps described?
91
+ - What transformations occur?
92
+ - What data flows through?
93
+
94
+ 2. IDENTIFY ACTORS from "Utilisateur Cible" and product description:
95
+ - Who are the users/stakeholders?
96
+ - What systems are mentioned?
97
+ - Is there AI/automation mentioned?
98
+
99
+ 3. IDENTIFY DECISION POINTS:
100
+ - Are there conditions, validations, approvals?
101
+ - Different paths based on data type or rules?
102
+ - Quality checks or enrichment loops?
103
+
104
+ 4. STRUCTURE THE FLOW:
105
+ - Start: Data source or trigger
106
+ - Middle: Processing steps (automated, AI, manual)
107
+ - End: Result storage or output
108
+
109
+ 5. ADD BUSINESS CONTEXT from "Fonctionnalités Clés":
110
+ - What specific operations are performed?
111
+ - What calculations or transformations?
112
+ - What enrichments or validations?"""
113
+
114
+ @staticmethod
115
+ def get_initial_prompt(pvb_data: dict) -> str:
116
+ """Generate prompt for initial diagram creation from PVB data."""
117
+ return f"""{DiagramPrompts.SYSTEM_PROMPT}
118
+
119
+ Now, analyze this Product Vision Board and create an OPERATIONAL PROCESS DIAGRAM:
120
+
121
+ {json.dumps(pvb_data, indent=2, ensure_ascii=False)}
122
+
123
+ YOUR TASK:
124
+ Based on the Product Vision Board above, infer and create a complete operational business process diagram.
125
+
126
+ ANALYSIS STEPS:
127
+
128
+ 1. **Extract the operational workflow** from "Description du Produit":
129
+ - What steps are described or implied?
130
+ - What is the sequence of operations?
131
+ - What data transformations occur?
132
+
133
+ 2. **Identify actors and their roles** from "Utilisateur Cible" and descriptions:
134
+ - Who are the human actors? (→ Orange boxes 👤)
135
+ - What systems are involved? (→ Blue boxes 🖥️)
136
+ - Is there AI/automation? (→ Green boxes 🤖)
137
+
138
+ 3. **Find decision points and validations**:
139
+ - Are there conditional branches? (Use diamond shapes {{}})
140
+ - Are there validation/approval steps?
141
+ - Multiple paths based on data type or business rules?
142
+
143
+ 4. **Structure the complete process**:
144
+ - START: Data source, trigger, or input (use [/ \] shape)
145
+ - MIDDLE: All processing steps in logical order
146
+ - END: Result storage or output (use [/ \] shape)
147
+
148
+ 5. **Add business intelligence** from "Fonctionnalités Clés":
149
+ - What calculations or enrichments occur?
150
+ - What specific operations are performed?
151
+
152
+ DIAGRAM REQUIREMENTS:
153
+
154
+ ✓ Use flowchart TD (top-down, vertical)
155
+ ✓ Create a "Légende" subgraph showing actor types
156
+ ✓ Create a main process subgraph with a descriptive title
157
+ ✓ Use proper emojis for each step type
158
+ ✓ Apply colors by actor type (blue=system, green=AI, orange=human)
159
+ ✓ Use decision diamonds {{}} when there are choices
160
+ ✓ Label all arrows with conditions when relevant
161
+ ✓ Keep labels concise but informative (use <br/> for line breaks)
162
+ ✓ Make it professional and business-ready
163
+
164
+ IMPORTANT:
165
+ - This is a PROCESS diagram, not a conceptual structure
166
+ - Show the FLOW of operations step by step
167
+ - Include ALL logical steps even if not explicitly stated in the PVB
168
+ - Think like a business analyst: what would the real operational process look like?
169
+
170
+ Respond with ONLY the Mermaid diagram in ```mermaid``` code blocks. No additional explanation."""
171
+
172
+ @staticmethod
173
+ def get_refinement_prompt(pvb_data: dict, current_diagram: str, user_feedback: str) -> str:
174
+ """Generate prompt for diagram refinement based on user feedback."""
175
+ return f"""You are refining an operational business process diagram.
176
+
177
+ CURRENT DIAGRAM:
178
+ ```mermaid
179
+ {current_diagram}
180
+ ```
181
+
182
+ ORIGINAL PRODUCT VISION BOARD:
183
+ {json.dumps(pvb_data, indent=2, ensure_ascii=False)}
184
+
185
+ USER REQUEST: "{user_feedback}"
186
+
187
+ YOUR TASK:
188
+ Modify the diagram according to the user's request while maintaining process logic and professional quality.
189
+
190
+ REFINEMENT GUIDELINES:
191
+
192
+ **Layout modifications:**
193
+ - "plus vertical" / "more vertical" → Ensure flowchart TD, arrange nodes top-to-bottom
194
+ - "plus horizontal" / "horizontal" → Change to flowchart LR
195
+ - "plus compact" / "compact" → Reduce spacing, group related items in subgraphs
196
+ - "plus aéré" / "spread out" → Add more intermediate steps
197
+
198
+ **Visual enhancements:**
199
+ - "plus de couleurs" / "add colors" → Ensure ALL nodes have colors based on actor type
200
+ - "ajouter des icônes" / "add icons" → Add relevant emojis to each step
201
+ - "plus gros" / "bigger" → Add more line breaks (<br/>) in labels
202
+ - "légende" / "legend" → Add or enhance the "Légende" subgraph
203
+
204
+ **Content modifications:**
205
+ - "plus de détails" / "more detail" → Add intermediate steps, split complex steps
206
+ - "simplifier" / "simplify" → Combine related steps, remove redundancy
207
+ - "ajouter X" / "add X" → Insert new step(s) in logical position
208
+ - "supprimer Y" / "remove Y" → Remove specified element(s)
209
+
210
+ **Process logic modifications:**
211
+ - "ajouter décision" / "add decision" → Add decision diamond {{}} with branches
212
+ - "ajouter validation" / "add validation" → Add human validation step (orange)
213
+ - "séparer les acteurs" / "separate actors" → Use subgraphs or swimlanes
214
+ - "ajouter boucle" / "add loop" → Add feedback/retry arrows
215
+
216
+ IMPORTANT RULES:
217
+ ✓ Maintain the operational process logic
218
+ ✓ Keep actor color coding (blue=system, green=AI, orange=human)
219
+ ✓ Preserve the sequential flow unless asked to change it
220
+ ✓ Keep labels clear and professional
221
+ ✓ Ensure valid Mermaid syntax
222
+ ✓ Include Légende subgraph if not present
223
+
224
+ Respond with ONLY the updated Mermaid diagram in ```mermaid``` code blocks. No explanation."""
225
+
226
+ @staticmethod
227
+ def get_chat_message(text: str) -> str:
228
+ """Format a regular chat message (not diagram-related)."""
229
+ return text
src/ai/qwen_zerogpu_analyzer.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Qwen model with ZeroGPU support for Hugging Face Spaces.
3
+ Uses transformers with @spaces.GPU decorator.
4
+ """
5
+ import torch
6
+ from typing import List, Dict
7
+ from transformers import AutoTokenizer, AutoModelForCausalLM
8
+ import spaces
9
+
10
+
11
+ class QwenZeroGPUAnalyzer:
12
+ """
13
+ Qwen3 model analyzer with ZeroGPU support.
14
+ Uses Qwen3-4B-Instruct for diagram generation.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ model_name: str = "Qwen/Qwen3-4B-Instruct"
20
+ ):
21
+ """
22
+ Initialize the Qwen ZeroGPU analyzer.
23
+
24
+ Args:
25
+ model_name: HuggingFace model ID
26
+ """
27
+ self.model_name = model_name
28
+ self.model = None
29
+ self.tokenizer = None
30
+
31
+ print(f"✓ Qwen ZeroGPU analyzer initialized (model will load on first inference)")
32
+ print(f" Model: {self.model_name}")
33
+
34
+ def _load_model(self):
35
+ """Load model and tokenizer (called on first inference)."""
36
+ if self.model is not None:
37
+ return
38
+
39
+ print(f"Loading model: {self.model_name}...")
40
+
41
+ # Load tokenizer
42
+ self.tokenizer = AutoTokenizer.from_pretrained(
43
+ self.model_name,
44
+ trust_remote_code=True
45
+ )
46
+
47
+ # Load model (will be moved to GPU by @spaces.GPU decorator)
48
+ self.model = AutoModelForCausalLM.from_pretrained(
49
+ self.model_name,
50
+ torch_dtype=torch.bfloat16,
51
+ device_map="auto",
52
+ trust_remote_code=True
53
+ )
54
+
55
+ print(f"✓ Model loaded: {self.model_name}")
56
+
57
+ @spaces.GPU(duration=60) # ZeroGPU decorator - max 60 seconds
58
+ def generate_response(self, conversation: List[Dict[str, str]], max_tokens: int = 4000) -> str:
59
+ """
60
+ Generate response from conversation history using ZeroGPU.
61
+
62
+ Args:
63
+ conversation: List of conversation messages with 'role' and 'content'
64
+ max_tokens: Maximum tokens to generate
65
+
66
+ Returns:
67
+ Generated response text
68
+ """
69
+ # Load model on first call
70
+ if self.model is None:
71
+ self._load_model()
72
+
73
+ # Apply chat template
74
+ prompt = self.tokenizer.apply_chat_template(
75
+ conversation,
76
+ tokenize=False,
77
+ add_generation_prompt=True
78
+ )
79
+
80
+ # Tokenize
81
+ inputs = self.tokenizer(prompt, return_tensors="pt")
82
+ inputs = {k: v.to(self.model.device) for k, v in inputs.items()}
83
+
84
+ # Generate with ZeroGPU
85
+ with torch.no_grad():
86
+ outputs = self.model.generate(
87
+ **inputs,
88
+ max_new_tokens=max_tokens,
89
+ temperature=0.2, # Low temperature for consistent diagrams
90
+ do_sample=False, # Greedy decoding for deterministic output
91
+ pad_token_id=self.tokenizer.eos_token_id
92
+ )
93
+
94
+ # Decode response (skip input tokens)
95
+ input_length = inputs["input_ids"].shape[1]
96
+ response = self.tokenizer.decode(
97
+ outputs[0][input_length:],
98
+ skip_special_tokens=True
99
+ )
100
+
101
+ return response.strip()
102
+
103
+ def cleanup_model(self):
104
+ """Cleanup (managed by ZeroGPU)."""
105
+ # ZeroGPU handles cleanup automatically
106
+ pass
src/core/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Core modules for PVB Flow"""
src/core/mermaid_encoder.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Encode Mermaid diagrams for Mermaid Live Editor URLs.
3
+
4
+ Format: JSON state with zlib compression + base64url encoding.
5
+ Based on: Claude Desktop MCP implementation for Mermaid Live Editor.
6
+ """
7
+ import zlib
8
+ import base64
9
+ import json
10
+
11
+
12
+ def encode_mermaid_for_url(mermaid_code: str) -> str:
13
+ """
14
+ Encode Mermaid diagram code for Mermaid Live Editor URL.
15
+
16
+ Uses the official Mermaid Live Editor format:
17
+ 1. Wrap code in JSON state object
18
+ 2. Compress with zlib.compress (level 9, WITH header)
19
+ 3. Base64url encode (+ → -, / → _, remove =)
20
+
21
+ Based on Claude Desktop MCP implementation for Mermaid Live Editor.
22
+
23
+ Args:
24
+ mermaid_code: Raw Mermaid diagram code
25
+
26
+ Returns:
27
+ Base64URL encoded string suitable for URL fragment
28
+ """
29
+ if not mermaid_code or not mermaid_code.strip():
30
+ return ""
31
+
32
+ # Create state object as per Mermaid Live Editor format
33
+ state = {
34
+ "code": mermaid_code,
35
+ "mermaid": {
36
+ "theme": "default"
37
+ },
38
+ "autoSync": True,
39
+ "updateDiagram": True
40
+ }
41
+
42
+ # Convert to JSON string
43
+ state_json = json.dumps(state)
44
+
45
+ # Encode to bytes
46
+ state_bytes = state_json.encode('utf-8')
47
+
48
+ # Compress using zlib.compress() with header (level 9, maximum compression)
49
+ # This matches the Claude Desktop MCP implementation
50
+ compressed = zlib.compress(state_bytes, level=9)
51
+
52
+ # Base64url encode (URL-safe variant)
53
+ # urlsafe_b64encode automatically does: + → -, / → _
54
+ encoded = base64.urlsafe_b64encode(compressed).decode('utf-8')
55
+
56
+ # Remove trailing = padding
57
+ base64url = encoded.rstrip('=')
58
+
59
+ return base64url
60
+
61
+
62
+ def generate_mermaid_chart_url(mermaid_code: str) -> str:
63
+ """
64
+ Generate a complete Mermaid Live Editor URL.
65
+
66
+ Args:
67
+ mermaid_code: Raw Mermaid diagram code
68
+
69
+ Returns:
70
+ Full URL to Mermaid Live Editor with encoded diagram
71
+ """
72
+ if not mermaid_code or not mermaid_code.strip():
73
+ return ""
74
+
75
+ encoded = encode_mermaid_for_url(mermaid_code)
76
+
77
+ # Use Mermaid Live Editor (official editor that works with pako encoding)
78
+ url = f"https://mermaid.live/edit#pako:{encoded}"
79
+
80
+ return url
src/core/mermaid_extractor.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utilities for extracting and validating Mermaid diagrams from LLM responses.
3
+ """
4
+ import re
5
+ from typing import Tuple, Optional
6
+
7
+
8
+ class MermaidExtractor:
9
+ """Extracts and validates Mermaid code from text."""
10
+
11
+ @staticmethod
12
+ def extract_mermaid_code(llm_response: str) -> Tuple[Optional[str], bool]:
13
+ """
14
+ Extract Mermaid code from LLM response.
15
+
16
+ Args:
17
+ llm_response: Full response from the LLM
18
+
19
+ Returns:
20
+ Tuple of (mermaid_code, is_valid)
21
+ """
22
+ # Pattern to match ```mermaid ... ```
23
+ pattern = r'```mermaid\n(.*?)\n```'
24
+ match = re.search(pattern, llm_response, re.DOTALL)
25
+
26
+ if match:
27
+ code = match.group(1).strip()
28
+ is_valid, _ = MermaidExtractor.validate_mermaid_syntax(code)
29
+ return code, is_valid
30
+
31
+ return None, False
32
+
33
+ @staticmethod
34
+ def validate_mermaid_syntax(code: str) -> Tuple[bool, str]:
35
+ """
36
+ Basic Mermaid syntax validation.
37
+
38
+ Args:
39
+ code: Mermaid diagram code
40
+
41
+ Returns:
42
+ Tuple of (is_valid, error_message)
43
+ """
44
+ if not code or not code.strip():
45
+ return False, "Empty Mermaid code"
46
+
47
+ # Check for diagram type declaration
48
+ diagram_types = ['flowchart', 'graph', 'sequenceDiagram', 'classDiagram',
49
+ 'stateDiagram', 'erDiagram', 'gantt', 'pie']
50
+
51
+ has_diagram_type = any(dtype in code for dtype in diagram_types)
52
+ if not has_diagram_type:
53
+ return False, "Missing diagram type declaration (flowchart, graph, etc.)"
54
+
55
+ # Check for connections (common syntax)
56
+ connection_patterns = ['-->', '->', '---', '-.->', '==>','-.->']
57
+ has_connections = any(conn in code for conn in connection_patterns)
58
+
59
+ if not has_connections:
60
+ return False, "No connections found in diagram"
61
+
62
+ # Check for at least one node definition
63
+ # Nodes are typically: ID[Label] or ID(Label) or ID{Label}
64
+ node_pattern = r'[A-Z]\d*[\[\(\{]'
65
+ if not re.search(node_pattern, code):
66
+ return False, "No nodes defined in diagram"
67
+
68
+ return True, "Valid Mermaid syntax"
69
+
70
+ @staticmethod
71
+ def format_for_display(mermaid_code: str, assistant_text: str = None) -> str:
72
+ """
73
+ Format Mermaid code for Gradio display.
74
+ Gradio v6 automatically renders Mermaid in chatbot.
75
+
76
+ Args:
77
+ mermaid_code: The Mermaid diagram code
78
+ assistant_text: Optional explanatory text from the assistant
79
+
80
+ Returns:
81
+ Formatted string with Mermaid code block
82
+ """
83
+ response = ""
84
+ if assistant_text:
85
+ response += assistant_text + "\n\n"
86
+ response += f"```mermaid\n{mermaid_code}\n```"
87
+ return response
88
+
89
+ @staticmethod
90
+ def extract_all_mermaid_blocks(text: str) -> list[str]:
91
+ """
92
+ Extract all Mermaid code blocks from text (if multiple exist).
93
+
94
+ Args:
95
+ text: Text potentially containing multiple Mermaid blocks
96
+
97
+ Returns:
98
+ List of Mermaid code strings
99
+ """
100
+ pattern = r'```mermaid\n(.*?)\n```'
101
+ matches = re.findall(pattern, text, re.DOTALL)
102
+ return [match.strip() for match in matches]
103
+
104
+
105
+ # Convenience functions
106
+ def extract_mermaid_code(llm_response: str) -> Tuple[Optional[str], bool]:
107
+ """Shorthand for MermaidExtractor.extract_mermaid_code()"""
108
+ return MermaidExtractor.extract_mermaid_code(llm_response)
109
+
110
+
111
+ def validate_mermaid_syntax(code: str) -> Tuple[bool, str]:
112
+ """Shorthand for MermaidExtractor.validate_mermaid_syntax()"""
113
+ return MermaidExtractor.validate_mermaid_syntax(code)
114
+
115
+
116
+ def format_for_display(mermaid_code: str, assistant_text: str = None) -> str:
117
+ """Shorthand for MermaidExtractor.format_for_display()"""
118
+ return MermaidExtractor.format_for_display(mermaid_code, assistant_text)
src/ui/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """UI modules for PVB Flow"""
src/ui/spaces_interface.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio v6 interface for Hugging Face Spaces.
3
+ Uses Mistral API for diagram generation.
4
+ """
5
+ import gradio as gr
6
+ import os
7
+ from typing import Tuple, List, Dict
8
+ from ..ai.qwen_zerogpu_analyzer import QwenZeroGPUAnalyzer
9
+ from ..ai.prompts_config import DiagramPrompts
10
+ from ..utils.json_validator import validate_pvb_json
11
+ from ..core.mermaid_extractor import extract_mermaid_code
12
+ from ..core.mermaid_encoder import generate_mermaid_chart_url
13
+
14
+
15
+ def create_spaces_interface():
16
+ """
17
+ Create Gradio interface for Hugging Face Spaces.
18
+
19
+ Returns:
20
+ Gradio Blocks demo
21
+ """
22
+
23
+ # Initialize Qwen ZeroGPU analyzer
24
+ try:
25
+ analyzer = QwenZeroGPUAnalyzer()
26
+ print("✅ Qwen ZeroGPU analyzer initialized")
27
+ except Exception as e:
28
+ print(f"⚠️ Warning: Could not initialize Qwen analyzer: {e}")
29
+ analyzer = None
30
+
31
+ def handle_message(
32
+ user_input: str,
33
+ conversation: List[Dict[str, str]],
34
+ current_diagram: str,
35
+ pvb_data: Dict
36
+ ) -> Tuple[List[Dict[str, str]], str, List[Dict], str, Dict, str]:
37
+ """Handle user message and generate response."""
38
+
39
+ if not analyzer:
40
+ error_msg = "❌ Model not initialized. Please check the Space logs."
41
+ conversation.append({"role": "user", "content": user_input})
42
+ conversation.append({"role": "assistant", "content": error_msg})
43
+ diagram_preview = f"```mermaid\n{current_diagram}\n```" if current_diagram else "Model not initialized"
44
+ return conversation, diagram_preview, conversation, current_diagram, pvb_data, ""
45
+
46
+ if not user_input or not user_input.strip():
47
+ diagram_preview = f"```mermaid\n{current_diagram}\n```" if current_diagram else "Diagram will appear here..."
48
+ return conversation, diagram_preview, conversation, current_diagram, pvb_data, ""
49
+
50
+ # Check if this is initial PVB input or refinement
51
+ if not pvb_data:
52
+ # Try to parse as PVB JSON
53
+ is_valid, parsed_pvb, error = validate_pvb_json(user_input)
54
+
55
+ if is_valid:
56
+ # Valid PVB JSON - generate initial diagram
57
+ pvb_data = parsed_pvb
58
+ prompt = DiagramPrompts.get_initial_prompt(pvb_data)
59
+ display_message = "Here's my Product Vision Board. Please generate a Mermaid diagram."
60
+ else:
61
+ # Not valid PVB JSON, treat as regular message
62
+ prompt = user_input
63
+ display_message = user_input
64
+ else:
65
+ # Refinement request
66
+ prompt = DiagramPrompts.get_refinement_prompt(pvb_data, current_diagram, user_input)
67
+ display_message = user_input
68
+
69
+ # Add display message to conversation
70
+ conversation.append({"role": "user", "content": display_message})
71
+
72
+ try:
73
+ # Create LLM conversation with the actual prompt
74
+ llm_conversation = conversation[:-1] # All messages except the last one
75
+ llm_conversation.append({"role": "user", "content": prompt})
76
+
77
+ # Generate response with Qwen ZeroGPU
78
+ response = analyzer.generate_response(llm_conversation)
79
+
80
+ # Extract Mermaid code from response
81
+ mermaid_code, is_valid = extract_mermaid_code(response)
82
+
83
+ if is_valid and mermaid_code:
84
+ current_diagram = mermaid_code
85
+
86
+ # Format diagram preview
87
+ diagram_preview = f"```mermaid\n{current_diagram}\n```" if current_diagram else "No diagram yet..."
88
+
89
+ # For chat display: show only text without the Mermaid code block
90
+ import re
91
+ chat_response = re.sub(r'```mermaid\n.*?\n```', '', response, flags=re.DOTALL).strip()
92
+
93
+ if not chat_response:
94
+ chat_response = "Diagramme généré avec succès ! Consultez le panneau de droite pour visualiser le résultat."
95
+
96
+ conversation.append({"role": "assistant", "content": chat_response})
97
+
98
+ except Exception as e:
99
+ error_message = f"Error generating response: {str(e)}"
100
+ conversation.append({"role": "assistant", "content": error_message})
101
+ diagram_preview = f"```mermaid\n{current_diagram}\n```" if current_diagram else "Error occurred"
102
+
103
+ return (
104
+ conversation,
105
+ diagram_preview,
106
+ conversation,
107
+ current_diagram,
108
+ pvb_data,
109
+ ""
110
+ )
111
+
112
+ def handle_clear() -> Tuple[List, str, List, str, Dict, str]:
113
+ """Clear all conversation and state."""
114
+ return (
115
+ [],
116
+ "Diagram will appear here...",
117
+ [],
118
+ "",
119
+ {},
120
+ ""
121
+ )
122
+
123
+ def handle_generate_link(diagram_preview: str) -> str:
124
+ """Generate Mermaid Live Editor link."""
125
+ import time
126
+ import hashlib
127
+ import re
128
+
129
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
130
+
131
+ # Extract Mermaid code from preview
132
+ mermaid_pattern = r'```mermaid\n(.*?)\n```'
133
+ match = re.search(mermaid_pattern, diagram_preview, re.DOTALL)
134
+
135
+ if not match:
136
+ return "⚠️ **Pas de diagramme à partager.** Veuillez d'abord générer un diagramme."
137
+
138
+ current_diagram = match.group(1).strip()
139
+
140
+ if not current_diagram or not current_diagram.strip():
141
+ return "⚠️ **Pas de diagramme à partager.** Veuillez d'abord générer un diagramme."
142
+
143
+ # Generate hash for verification
144
+ diagram_hash = hashlib.md5(current_diagram.encode()).hexdigest()[:8]
145
+
146
+ url = generate_mermaid_chart_url(current_diagram)
147
+
148
+ if url:
149
+ first_line = current_diagram.split('\n')[0] if current_diagram else 'N/A'
150
+
151
+ return f"""### 🔗 Lien Mermaid Live Editor
152
+
153
+ **Généré à:** {timestamp} | **Hash:** `{diagram_hash}`
154
+ **Diagramme:** `{first_line}` ({len(current_diagram)} caractères)
155
+
156
+ [**👉 Cliquez ici pour ouvrir votre diagramme dans Mermaid Live Editor**]({url})
157
+
158
+ Ou copiez le lien ci-dessous :
159
+ ```
160
+ {url}
161
+ ```
162
+
163
+ **Ce que vous pouvez faire sur Mermaid Live Editor :**
164
+ - ✏️ Éditer le diagramme en temps réel
165
+ - 📥 Exporter en PNG, SVG, ou PDF
166
+ - 🔗 Partager avec votre équipe
167
+ - 💾 Télécharger le code ou l'image
168
+ """
169
+ else:
170
+ return "❌ **Erreur lors de la génération du lien.** Veuillez réessayer."
171
+
172
+ # Build Gradio interface
173
+ with gr.Blocks(title="Product Vision Board → Mermaid Diagram") as demo:
174
+ # Header
175
+ gr.Markdown(
176
+ """
177
+ # 📊 Product Vision Board → Mermaid Diagram Generator
178
+ **Transform your Product Vision Board into professional Mermaid flowcharts**
179
+ """
180
+ )
181
+
182
+ # Instructions
183
+ with gr.Accordion("📖 How to use", open=False):
184
+ gr.Markdown(
185
+ """
186
+ ### Getting Started
187
+
188
+ 1. **Paste your Product Vision Board JSON** in the chat input (see example below)
189
+ 2. **Wait for the diagram** to be generated
190
+ 3. **Refine the diagram** by chatting (e.g., "make it more vertical", "add more colors", "simplify")
191
+
192
+ ### Example Product Vision Board JSON
193
+ ```json
194
+ {
195
+ "1. Utilisateur Cible": [
196
+ "Passionnés de cuisine amateur",
197
+ "Professionnels de la restauration"
198
+ ],
199
+ "2. Description du Produit": [
200
+ "Application de gestion de recettes avec suggestions personnalisées",
201
+ "Planification automatique des repas de la semaine"
202
+ ],
203
+ "3. Fonctionnalités Clés": [
204
+ "Recherche de recettes par ingrédients disponibles",
205
+ "Génération automatique de liste de courses",
206
+ "Suggestions basées sur les préférences alimentaires"
207
+ ],
208
+ "4. Enjeux et Indicateurs": [
209
+ "Réduire le gaspillage alimentaire de 30%",
210
+ "Atteindre 100 000 utilisateurs actifs en 6 mois"
211
+ ],
212
+ "Summary": "Simplifier la planification des repas et réduire le gaspillage alimentaire"
213
+ }
214
+ ```
215
+
216
+ ### Tips
217
+ - The chatbot will auto-render Mermaid diagrams
218
+ - You can request layout changes (vertical/horizontal)
219
+ - Ask for visual enhancements (colors, icons, subgraphs)
220
+ - Iterate until you're happy with the result!
221
+ """
222
+ )
223
+
224
+ # Main content: Two-column layout
225
+ with gr.Row():
226
+ # LEFT COLUMN: Chatbot
227
+ with gr.Column(scale=1):
228
+ chatbot = gr.Chatbot(
229
+ value=[],
230
+ height=650,
231
+ label="Conversation"
232
+ )
233
+
234
+ # Input area
235
+ msg_input = gr.Textbox(
236
+ placeholder="Paste your Product Vision Board JSON or ask for diagram refinements...",
237
+ lines=5,
238
+ show_label=False,
239
+ max_lines=10
240
+ )
241
+
242
+ # Action buttons
243
+ with gr.Row():
244
+ send_btn = gr.Button("📤 Send", variant="primary")
245
+ clear_btn = gr.Button("🗑️ Clear", variant="secondary")
246
+
247
+ # RIGHT COLUMN: Diagram Preview
248
+ with gr.Column(scale=1):
249
+ gr.Markdown("### 📈 Diagram Preview")
250
+ diagram_preview = gr.Markdown(
251
+ value="*Paste your Product Vision Board JSON to generate a diagram...*",
252
+ height=500
253
+ )
254
+
255
+ # Diagram actions
256
+ with gr.Row():
257
+ open_chart_btn = gr.Button("🔗 Generate Mermaid Live Link", variant="primary")
258
+
259
+ # URL display area
260
+ mermaid_url_display = gr.Markdown(
261
+ value="",
262
+ label="Mermaid Live Editor Link"
263
+ )
264
+
265
+ gr.Markdown("*Click the button to generate a shareable link. Then click the link to open in Mermaid Live Editor!*")
266
+
267
+ # State management
268
+ conversation_state = gr.State([])
269
+ diagram_state = gr.State("")
270
+ pvb_state = gr.State({})
271
+
272
+ # Event handlers
273
+ send_btn.click(
274
+ fn=handle_message,
275
+ inputs=[msg_input, conversation_state, diagram_state, pvb_state],
276
+ outputs=[chatbot, diagram_preview, conversation_state, diagram_state, pvb_state, msg_input]
277
+ )
278
+
279
+ msg_input.submit(
280
+ fn=handle_message,
281
+ inputs=[msg_input, conversation_state, diagram_state, pvb_state],
282
+ outputs=[chatbot, diagram_preview, conversation_state, diagram_state, pvb_state, msg_input]
283
+ )
284
+
285
+ clear_btn.click(
286
+ fn=handle_clear,
287
+ outputs=[chatbot, diagram_preview, conversation_state, diagram_state, pvb_state, mermaid_url_display]
288
+ )
289
+
290
+ open_chart_btn.click(
291
+ fn=handle_generate_link,
292
+ inputs=[diagram_preview],
293
+ outputs=[mermaid_url_display]
294
+ )
295
+
296
+ # Footer
297
+ gr.Markdown(
298
+ """
299
+ ---
300
+ Made with ❤️ using [Gradio v6](https://gradio.app) • Powered by Qwen3-4B-Instruct with ZeroGPU
301
+ """
302
+ )
303
+
304
+ return demo
src/utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Utility modules for PVB Flow"""
src/utils/json_validator.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ JSON validation utilities for Product Vision Board data.
3
+ """
4
+ import json
5
+ from typing import Tuple, Dict, Optional
6
+
7
+
8
+ class PVBValidator:
9
+ """Validates and parses Product Vision Board JSON data."""
10
+
11
+ # Expected sections in a Product Vision Board
12
+ EXPECTED_SECTIONS = [
13
+ "1. Utilisateur Cible",
14
+ "2. Description du Produit",
15
+ "3. Fonctionnalités Clés",
16
+ "4. Enjeux et Indicateurs"
17
+ ]
18
+
19
+ @staticmethod
20
+ def validate_pvb_json(user_input: str) -> Tuple[bool, Optional[Dict], Optional[str]]:
21
+ """
22
+ Validate if the user input is valid Product Vision Board JSON.
23
+
24
+ Args:
25
+ user_input: String that might be JSON
26
+
27
+ Returns:
28
+ Tuple of (is_valid, parsed_data, error_message)
29
+ """
30
+ # Try to parse as JSON
31
+ try:
32
+ data = json.loads(user_input)
33
+ except json.JSONDecodeError as e:
34
+ return False, None, f"Invalid JSON format: {str(e)}"
35
+
36
+ # Check if it's a dictionary
37
+ if not isinstance(data, dict):
38
+ return False, None, "JSON must be an object/dictionary, not an array or primitive"
39
+
40
+ # Check for at least some expected sections
41
+ found_sections = [section for section in PVBValidator.EXPECTED_SECTIONS if section in data]
42
+
43
+ if len(found_sections) == 0:
44
+ # Not a PVB, might be a regular JSON
45
+ return False, None, "No Product Vision Board sections found"
46
+
47
+ # Validate that sections contain lists
48
+ for section in found_sections:
49
+ if not isinstance(data[section], list):
50
+ return False, None, f"Section '{section}' must be a list"
51
+
52
+ # Valid PVB JSON
53
+ return True, data, None
54
+
55
+ @staticmethod
56
+ def is_pvb_like(user_input: str) -> bool:
57
+ """
58
+ Quick check if input looks like PVB JSON (without full validation).
59
+
60
+ Args:
61
+ user_input: User input string
62
+
63
+ Returns:
64
+ True if it looks like PVB JSON
65
+ """
66
+ # Check if it starts with { and contains at least one expected section
67
+ if not user_input.strip().startswith("{"):
68
+ return False
69
+
70
+ for section in PVBValidator.EXPECTED_SECTIONS:
71
+ if section in user_input:
72
+ return True
73
+
74
+ return False
75
+
76
+ @staticmethod
77
+ def extract_summary(pvb_data: Dict) -> str:
78
+ """
79
+ Extract or generate a summary from PVB data.
80
+
81
+ Args:
82
+ pvb_data: Parsed PVB dictionary
83
+
84
+ Returns:
85
+ Summary string
86
+ """
87
+ if "Summary" in pvb_data:
88
+ return pvb_data["Summary"]
89
+
90
+ # Generate basic summary from sections
91
+ summary_parts = []
92
+ if "1. Utilisateur Cible" in pvb_data:
93
+ users = pvb_data["1. Utilisateur Cible"]
94
+ summary_parts.append(f"Target users: {', '.join(users)}")
95
+
96
+ if "4. Enjeux et Indicateurs" in pvb_data:
97
+ objectives = pvb_data["4. Enjeux et Indicateurs"]
98
+ if objectives:
99
+ summary_parts.append(f"Key objective: {objectives[0]}")
100
+
101
+ return " | ".join(summary_parts) if summary_parts else "Product Vision Board"
102
+
103
+
104
+ # Convenience functions
105
+ def validate_pvb_json(user_input: str) -> Tuple[bool, Optional[Dict], Optional[str]]:
106
+ """Shorthand for PVBValidator.validate_pvb_json()"""
107
+ return PVBValidator.validate_pvb_json(user_input)
108
+
109
+
110
+ def is_pvb_like(user_input: str) -> bool:
111
+ """Shorthand for PVBValidator.is_pvb_like()"""
112
+ return PVBValidator.is_pvb_like(user_input)