openfree commited on
Commit
8f54e55
ยท
verified ยท
1 Parent(s): b8790f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -19
app.py CHANGED
@@ -3,6 +3,8 @@ import os
3
  import sys
4
  import json
5
  import requests
 
 
6
 
7
  # ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์™€ ํฐํŠธ ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ •
8
  CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -77,8 +79,88 @@ from wbs_diagram_generator import generate_wbs_diagram
77
 
78
  from sample_data import CONCEPT_MAP_JSON, SYNOPTIC_CHART_JSON, RADIAL_DIAGRAM_JSON, PROCESS_FLOW_JSON, WBS_DIAGRAM_JSON
79
 
80
- # LLM API ํ•จ์ˆ˜
81
- def call_llm_api(prompt, diagram_type):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  """Friendli AI API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ JSON ์ƒ์„ฑ"""
83
  token = os.environ.get("FRIENDLI_TOKEN") or "YOUR_FRIENDLI_TOKEN"
84
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
@@ -193,9 +275,16 @@ def call_llm_api(prompt, diagram_type):
193
  }"""
194
  }
195
 
 
 
 
 
 
 
 
196
  system_prompt = f"""You are a helpful assistant that generates JSON structures for diagrams.
197
  {json_guides.get(diagram_type, '')}
198
-
199
  Important rules:
200
  1. Generate ONLY valid JSON without any explanation or markdown formatting
201
  2. The JSON must follow the EXACT structure shown above - do not change field names
@@ -205,7 +294,8 @@ Important rules:
205
  6. Ensure all connections reference existing node IDs
206
  7. For Process Flow: 'type' can be: "process", "decision", "start", "end", "io"
207
  8. For nested structures (Concept Map, Synoptic Chart, Radial, WBS), include empty 'subnodes' or 'subtasks' arrays when there are no children
208
- 9. Do not add any additional fields not shown in the example structure"""
 
209
 
210
  payload = {
211
  "model": "dep89a2fld32mcm",
@@ -270,13 +360,29 @@ Important rules:
270
  print(f"LLM API Error: {str(e)}")
271
  return json.dumps({"error": str(e)})
272
 
273
- def generate_with_llm(prompt, diagram_type, output_format):
274
  """LLM์œผ๋กœ JSON์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์ด์–ด๊ทธ๋žจ์„ ์ƒ์„ฑ"""
275
  if not prompt:
276
- return None, "Please enter a prompt"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
- # LLM์œผ๋กœ JSON ์ƒ์„ฑ
279
- generated_json = call_llm_api(prompt, diagram_type)
280
 
281
  try:
282
  # JSON ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
@@ -286,13 +392,13 @@ def generate_with_llm(prompt, diagram_type, output_format):
286
  # JSON ๊ตฌ์กฐ ๊ฒ€์ฆ
287
  if diagram_type in ["Concept Map", "Synoptic Chart", "Radial Diagram"]:
288
  if "central_node" not in json_data or "nodes" not in json_data:
289
- return None, f"Invalid JSON structure for {diagram_type}. Missing 'central_node' or 'nodes'. Generated JSON:\n{json_str}"
290
  elif diagram_type == "Process Flow":
291
  if "start_node" not in json_data or "nodes" not in json_data or "connections" not in json_data:
292
- return None, f"Invalid JSON structure for Process Flow. Missing 'start_node', 'nodes', or 'connections'. Generated JSON:\n{json_str}"
293
  elif diagram_type == "WBS Diagram":
294
  if "project_title" not in json_data or "phases" not in json_data:
295
- return None, f"Invalid JSON structure for WBS. Missing 'project_title' or 'phases'. Generated JSON:\n{json_str}"
296
 
297
  # ๋‹ค์ด์–ด๊ทธ๋žจ ์ƒ์„ฑ
298
  try:
@@ -307,21 +413,21 @@ def generate_with_llm(prompt, diagram_type, output_format):
307
  elif diagram_type == "WBS Diagram":
308
  diagram = generate_wbs_diagram(json_str, output_format)
309
  else:
310
- return None, "Invalid diagram type"
311
 
312
  # ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐ˜ํ™˜๋œ ๊ฒฝ์šฐ
313
  if isinstance(diagram, str) and diagram.startswith("Error:"):
314
- return None, f"Diagram generation error: {diagram}\n\nGenerated JSON:\n{json_str}"
315
 
316
- return diagram, json_str
317
 
318
  except Exception as e:
319
- return None, f"Error generating diagram: {str(e)}\n\nGenerated JSON:\n{json_str}"
320
 
321
  except json.JSONDecodeError as e:
322
- return None, f"Invalid JSON generated: {str(e)}\n\nGenerated content:\n{generated_json}"
323
  except Exception as e:
324
- return None, f"Unexpected error: {str(e)}\n\nGenerated content:\n{generated_json}"
325
 
326
  if __name__ == "__main__":
327
  DEFAULT_BASE_COLOR = '#19191a'
@@ -482,6 +588,23 @@ if __name__ == "__main__":
482
  margin-bottom: 20px;
483
  }
484
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  /* ๋ฐ˜์‘ํ˜• ๋””์ž์ธ */
486
  @media (max-width: 768px) {
487
  .main-container {
@@ -541,8 +664,22 @@ if __name__ == "__main__":
541
  interactive=True
542
  )
543
 
 
 
 
 
 
 
544
  generate_btn = gr.Button("โœจ Generate with AI", variant="primary", size="lg")
545
 
 
 
 
 
 
 
 
 
546
  generated_json_output = gr.Textbox(
547
  label="๐Ÿ“„ Generated JSON",
548
  lines=15,
@@ -558,10 +695,20 @@ if __name__ == "__main__":
558
  height=600
559
  )
560
 
 
 
 
 
 
 
 
 
 
 
561
  generate_btn.click(
562
  fn=generate_with_llm,
563
- inputs=[prompt_input, diagram_type_select, output_format_radio],
564
- outputs=[ai_output_image, generated_json_output]
565
  )
566
 
567
  with gr.Row(elem_classes=["panel-section"]):
 
3
  import sys
4
  import json
5
  import requests
6
+ import re
7
+ from typing import List, Tuple
8
 
9
  # ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์™€ ํฐํŠธ ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ •
10
  CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
 
79
 
80
  from sample_data import CONCEPT_MAP_JSON, SYNOPTIC_CHART_JSON, RADIAL_DIAGRAM_JSON, PROCESS_FLOW_JSON, WBS_DIAGRAM_JSON
81
 
82
+ # ๋ธŒ๋ ˆ์ด๋ธŒ ์„œ์น˜ API ํ•จ์ˆ˜
83
+ def brave_search(query: str) -> List[dict]:
84
+ """๋ธŒ๋ ˆ์ด๋ธŒ ์„œ์น˜ API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰"""
85
+ api_key = os.getenv("BSEARCH_API")
86
+ if not api_key:
87
+ print("Warning: BSEARCH_API environment variable not set")
88
+ return []
89
+
90
+ url = "https://api.search.brave.com/res/v1/web/search"
91
+ headers = {
92
+ "Accept": "application/json",
93
+ "X-Subscription-Token": api_key
94
+ }
95
+ params = {
96
+ "q": query,
97
+ "count": 5 # ์ƒ์œ„ 5๊ฐœ ๊ฒฐ๊ณผ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ
98
+ }
99
+
100
+ try:
101
+ response = requests.get(url, headers=headers, params=params, timeout=10)
102
+ if response.status_code == 200:
103
+ data = response.json()
104
+ results = []
105
+ if "web" in data and "results" in data["web"]:
106
+ for result in data["web"]["results"][:5]:
107
+ results.append({
108
+ "title": result.get("title", ""),
109
+ "description": result.get("description", ""),
110
+ "url": result.get("url", "")
111
+ })
112
+ return results
113
+ else:
114
+ print(f"Brave Search API error: {response.status_code}")
115
+ return []
116
+ except Exception as e:
117
+ print(f"Brave Search error: {str(e)}")
118
+ return []
119
+
120
+ def extract_keywords(prompt: str, diagram_type: str) -> str:
121
+ """ํ”„๋กฌํ”„ํŠธ์—์„œ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ ์ถ”์ถœ"""
122
+ # ๋‹ค์ด์–ด๊ทธ๋žจ ํƒ€์ž… ์ œ๊ฑฐ
123
+ prompt_clean = prompt.lower()
124
+
125
+ # ์ผ๋ฐ˜์ ์ธ ์š”์ฒญ ํ‘œํ˜„ ์ œ๊ฑฐ
126
+ remove_patterns = [
127
+ r"create\s+a?\s*", r"make\s+a?\s*", r"generate\s+a?\s*", r"build\s+a?\s*",
128
+ r"design\s+a?\s*", r"develop\s+a?\s*", r"show\s+me\s*", r"i\s+need\s+a?\s*",
129
+ r"diagram\s*", r"chart\s*", r"map\s*", r"flow\s*", r"๋ฅผ\s*๋งŒ๋“ค์–ด\s*",
130
+ r"์ƒ์„ฑํ•ด\s*", r"๊ทธ๋ ค\s*", r"๋ณด์—ฌ\s*", r"์„ค๊ณ„\s*", r"๊ตฌ์ถ•\s*"
131
+ ]
132
+
133
+ for pattern in remove_patterns:
134
+ prompt_clean = re.sub(pattern, "", prompt_clean)
135
+
136
+ # ์ฃผ์š” ๋ช…์‚ฌ์™€ ํ•ต์‹ฌ ์šฉ์–ด๋งŒ ์ถ”์ถœ
137
+ # ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•: ๊ธธ์ด๊ฐ€ 3์ž ์ด์ƒ์ธ ๋‹จ์–ด๋“ค์„ ์ถ”์ถœ
138
+ words = prompt_clean.split()
139
+ keywords = []
140
+
141
+ for word in words:
142
+ # ๋ถˆํ•„์š”ํ•œ ๋‹จ์–ด ์ œ์™ธ
143
+ if len(word) > 2 and word not in ["the", "and", "for", "with", "that", "this", "from", "about"]:
144
+ keywords.append(word)
145
+
146
+ # ์ตœ๋Œ€ 5๊ฐœ ํ‚ค์›Œ๋“œ๋งŒ ์‚ฌ์šฉ
147
+ return " ".join(keywords[:5])
148
+
149
+ def enrich_prompt_with_search(prompt: str, diagram_type: str, search_results: List[dict]) -> str:
150
+ """๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋ณด๊ฐ•"""
151
+ if not search_results:
152
+ return prompt
153
+
154
+ # ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—์„œ ์œ ์šฉํ•œ ์ •๋ณด ์ถ”์ถœ
155
+ enrichment = "\n\nAdditional context from web search:\n"
156
+ for i, result in enumerate(search_results[:3]): # ์ƒ์œ„ 3๊ฐœ๋งŒ ์‚ฌ์šฉ
157
+ enrichment += f"- {result['title']}: {result['description']}\n"
158
+
159
+ enriched_prompt = f"{prompt}{enrichment}"
160
+ return enriched_prompt
161
+
162
+ # LLM API ํ•จ์ˆ˜ ์ˆ˜์ •
163
+ def call_llm_api(prompt, diagram_type, use_search=False, search_results=None):
164
  """Friendli AI API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ JSON ์ƒ์„ฑ"""
165
  token = os.environ.get("FRIENDLI_TOKEN") or "YOUR_FRIENDLI_TOKEN"
166
  url = "https://api.friendli.ai/dedicated/v1/chat/completions"
 
275
  }"""
276
  }
277
 
278
+ # ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์œผ๋ฉด ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ์ถ”๊ฐ€
279
+ search_context = ""
280
+ if use_search and search_results:
281
+ search_context = "\n\nUse the following search results to enrich the diagram content:\n"
282
+ for result in search_results[:3]:
283
+ search_context += f"- {result['title']}: {result['description']}\n"
284
+
285
  system_prompt = f"""You are a helpful assistant that generates JSON structures for diagrams.
286
  {json_guides.get(diagram_type, '')}
287
+ {search_context}
288
  Important rules:
289
  1. Generate ONLY valid JSON without any explanation or markdown formatting
290
  2. The JSON must follow the EXACT structure shown above - do not change field names
 
294
  6. Ensure all connections reference existing node IDs
295
  7. For Process Flow: 'type' can be: "process", "decision", "start", "end", "io"
296
  8. For nested structures (Concept Map, Synoptic Chart, Radial, WBS), include empty 'subnodes' or 'subtasks' arrays when there are no children
297
+ 9. Do not add any additional fields not shown in the example structure
298
+ 10. If search results are provided, incorporate relevant information into the diagram content"""
299
 
300
  payload = {
301
  "model": "dep89a2fld32mcm",
 
360
  print(f"LLM API Error: {str(e)}")
361
  return json.dumps({"error": str(e)})
362
 
363
+ def generate_with_llm(prompt, diagram_type, output_format, use_search):
364
  """LLM์œผ๋กœ JSON์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์ด์–ด๊ทธ๋žจ์„ ์ƒ์„ฑ"""
365
  if not prompt:
366
+ return None, "Please enter a prompt", ""
367
+
368
+ search_results = []
369
+ search_info = ""
370
+
371
+ # ๋ธŒ๋ ˆ์ด๋ธŒ ์„œ์น˜ ์‚ฌ์šฉ ์‹œ
372
+ if use_search:
373
+ keywords = extract_keywords(prompt, diagram_type)
374
+ if keywords:
375
+ search_info = f"๐Ÿ” Searching for: {keywords}\n"
376
+ search_results = brave_search(keywords)
377
+ if search_results:
378
+ search_info += f"โœ… Found {len(search_results)} relevant results\n\n"
379
+ for i, result in enumerate(search_results[:3]):
380
+ search_info += f"{i+1}. {result['title']}\n {result['description'][:100]}...\n\n"
381
+ else:
382
+ search_info += "โŒ No search results found\n\n"
383
 
384
+ # LLM์œผ๋กœ JSON ์ƒ์„ฑ (๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํฌํ•จ)
385
+ generated_json = call_llm_api(prompt, diagram_type, use_search, search_results)
386
 
387
  try:
388
  # JSON ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
 
392
  # JSON ๊ตฌ์กฐ ๊ฒ€์ฆ
393
  if diagram_type in ["Concept Map", "Synoptic Chart", "Radial Diagram"]:
394
  if "central_node" not in json_data or "nodes" not in json_data:
395
+ return None, f"Invalid JSON structure for {diagram_type}. Missing 'central_node' or 'nodes'. Generated JSON:\n{json_str}", search_info
396
  elif diagram_type == "Process Flow":
397
  if "start_node" not in json_data or "nodes" not in json_data or "connections" not in json_data:
398
+ return None, f"Invalid JSON structure for Process Flow. Missing 'start_node', 'nodes', or 'connections'. Generated JSON:\n{json_str}", search_info
399
  elif diagram_type == "WBS Diagram":
400
  if "project_title" not in json_data or "phases" not in json_data:
401
+ return None, f"Invalid JSON structure for WBS. Missing 'project_title' or 'phases'. Generated JSON:\n{json_str}", search_info
402
 
403
  # ๋‹ค์ด์–ด๊ทธ๋žจ ์ƒ์„ฑ
404
  try:
 
413
  elif diagram_type == "WBS Diagram":
414
  diagram = generate_wbs_diagram(json_str, output_format)
415
  else:
416
+ return None, "Invalid diagram type", search_info
417
 
418
  # ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐ˜ํ™˜๋œ ๊ฒฝ์šฐ
419
  if isinstance(diagram, str) and diagram.startswith("Error:"):
420
+ return None, f"Diagram generation error: {diagram}\n\nGenerated JSON:\n{json_str}", search_info
421
 
422
+ return diagram, json_str, search_info
423
 
424
  except Exception as e:
425
+ return None, f"Error generating diagram: {str(e)}\n\nGenerated JSON:\n{json_str}", search_info
426
 
427
  except json.JSONDecodeError as e:
428
+ return None, f"Invalid JSON generated: {str(e)}\n\nGenerated content:\n{generated_json}", search_info
429
  except Exception as e:
430
+ return None, f"Unexpected error: {str(e)}\n\nGenerated content:\n{generated_json}", search_info
431
 
432
  if __name__ == "__main__":
433
  DEFAULT_BASE_COLOR = '#19191a'
 
588
  margin-bottom: 20px;
589
  }
590
 
591
+ /* ๊ฒ€์ƒ‰ ์ •๋ณด ์Šคํƒ€์ผ */
592
+ .search-info {
593
+ background: #f8f9fa;
594
+ padding: 15px;
595
+ border-radius: 10px;
596
+ margin-top: 10px;
597
+ border-left: 4px solid #667eea;
598
+ }
599
+
600
+ /* ์ฒดํฌ๋ฐ•์Šค ์Šคํƒ€์ผ */
601
+ .gr-checkbox {
602
+ margin-top: 15px;
603
+ padding: 10px;
604
+ background: #f8f9fa;
605
+ border-radius: 8px;
606
+ }
607
+
608
  /* ๋ฐ˜์‘ํ˜• ๋””์ž์ธ */
609
  @media (max-width: 768px) {
610
  .main-container {
 
664
  interactive=True
665
  )
666
 
667
+ use_search_checkbox = gr.Checkbox(
668
+ label="๐Ÿ” Use Brave Search to enhance content",
669
+ value=False,
670
+ info="Search the web for relevant information to enrich your diagram"
671
+ )
672
+
673
  generate_btn = gr.Button("โœจ Generate with AI", variant="primary", size="lg")
674
 
675
+ search_info_output = gr.Textbox(
676
+ label="๐Ÿ” Search Information",
677
+ lines=8,
678
+ interactive=False,
679
+ visible=False,
680
+ elem_classes=["search-info"]
681
+ )
682
+
683
  generated_json_output = gr.Textbox(
684
  label="๐Ÿ“„ Generated JSON",
685
  lines=15,
 
695
  height=600
696
  )
697
 
698
+ # ๊ฒ€์ƒ‰ ์ฒดํฌ๋ฐ•์Šค ๋ณ€๊ฒฝ ์‹œ ๊ฒ€์ƒ‰ ์ •๋ณด ์ถœ๋ ฅ ํ‘œ์‹œ/์ˆจ๊น€
699
+ def toggle_search_info(use_search):
700
+ return gr.update(visible=use_search)
701
+
702
+ use_search_checkbox.change(
703
+ fn=toggle_search_info,
704
+ inputs=[use_search_checkbox],
705
+ outputs=[search_info_output]
706
+ )
707
+
708
  generate_btn.click(
709
  fn=generate_with_llm,
710
+ inputs=[prompt_input, diagram_type_select, output_format_radio, use_search_checkbox],
711
+ outputs=[ai_output_image, generated_json_output, search_info_output]
712
  )
713
 
714
  with gr.Row(elem_classes=["panel-section"]):