Spaces:
Sleeping
Sleeping
cho2-0923 commited on
Commit ·
014a313
1
Parent(s): 95ee8a8
Refactor app structure by separating data management and node functions into dedicated modules. Implement data initialization on app start and streamline node and idea handling. Update UI interactions for improved functionality.
Browse files- app.py +13 -283
- data_manager.py +48 -0
- idea_functions.py +48 -0
- node_functions.py +105 -0
app.py
CHANGED
|
@@ -3,288 +3,18 @@ import json
|
|
| 3 |
import os
|
| 4 |
import pandas as pd
|
| 5 |
from typing import List, Dict, Any
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
"""아이디어 데이터를 JSON 파일로 저장"""
|
| 19 |
-
with open('ideas_data.json', 'w', encoding='utf-8') as f:
|
| 20 |
-
json.dump(ideas_data, f, ensure_ascii=False, indent=2)
|
| 21 |
-
|
| 22 |
-
def load_nodes():
|
| 23 |
-
"""저장된 노드 데이터 불러오기"""
|
| 24 |
-
global nodes_data
|
| 25 |
-
if os.path.exists('nodes_data.json'):
|
| 26 |
-
try:
|
| 27 |
-
with open('nodes_data.json', 'r', encoding='utf-8') as f:
|
| 28 |
-
nodes_data = json.load(f)
|
| 29 |
-
except (json.JSONDecodeError, ValueError):
|
| 30 |
-
nodes_data = []
|
| 31 |
-
print("nodes_data.json 파일이 손상되었거나 비어있습니다. 새로 시작합니다.")
|
| 32 |
-
else:
|
| 33 |
-
nodes_data = []
|
| 34 |
-
|
| 35 |
-
def load_ideas():
|
| 36 |
-
"""저장된 아이디어 데이터 불러오기"""
|
| 37 |
-
global ideas_data
|
| 38 |
-
if os.path.exists('ideas_data.json'):
|
| 39 |
-
try:
|
| 40 |
-
with open('ideas_data.json', 'r', encoding='utf-8') as f:
|
| 41 |
-
ideas_data = json.load(f)
|
| 42 |
-
except (json.JSONDecodeError, ValueError):
|
| 43 |
-
ideas_data = []
|
| 44 |
-
print("ideas_data.json 파일이 손상되었거나 비어있습니다. 새로 시작합니다.")
|
| 45 |
-
else:
|
| 46 |
-
ideas_data = []
|
| 47 |
-
|
| 48 |
-
# 1. 포폴 업로드 탭 함수들
|
| 49 |
-
def upload_portfolio_files(files):
|
| 50 |
-
"""포트폴리오 파일 업로드 처리"""
|
| 51 |
-
if not files:
|
| 52 |
-
return "파일을 선택해주세요.", ""
|
| 53 |
-
|
| 54 |
-
uploaded_files = []
|
| 55 |
-
for file in files:
|
| 56 |
-
if file:
|
| 57 |
-
uploaded_files.append(f"📄 {file.name}")
|
| 58 |
-
|
| 59 |
-
files_display = "\n".join(uploaded_files)
|
| 60 |
-
return f"업로드된 파일:\n{files_display}", files_display
|
| 61 |
-
|
| 62 |
-
def process_uploaded_files(files_display):
|
| 63 |
-
"""업로드된 파일들을 AI로 분석하여 노드 생성"""
|
| 64 |
-
if not files_display:
|
| 65 |
-
return "업로드된 파일이 없습니다."
|
| 66 |
-
|
| 67 |
-
# TODO: 실제 AI 분석 로직 구현
|
| 68 |
-
sample_node = {
|
| 69 |
-
"title": "AI 분석된 프로젝트",
|
| 70 |
-
"solution": "업로드된 파일에서 분석된 솔루션과 핵심 기능들을 포함한 상세 설명",
|
| 71 |
-
"tags": ["AI", "데이터분석", "웹개발"],
|
| 72 |
-
"source": "파일 업로드"
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
nodes_data.append(sample_node)
|
| 76 |
-
save_nodes()
|
| 77 |
-
|
| 78 |
-
return "파일 분석이 완료되어 노드가 생성되었습니다!"
|
| 79 |
-
|
| 80 |
-
# 2. 노드 입력하기 탭 함수들
|
| 81 |
-
def add_keyword(keyword, current_tags):
|
| 82 |
-
"""키워드 추가"""
|
| 83 |
-
if not keyword:
|
| 84 |
-
return current_tags, "키워드를 입력해주세요."
|
| 85 |
-
|
| 86 |
-
if current_tags:
|
| 87 |
-
tags_list = [tag.strip() for tag in current_tags.split(',') if tag.strip()]
|
| 88 |
-
else:
|
| 89 |
-
tags_list = []
|
| 90 |
-
|
| 91 |
-
if keyword not in tags_list:
|
| 92 |
-
tags_list.append(keyword)
|
| 93 |
-
updated_tags = ', '.join(tags_list)
|
| 94 |
-
return updated_tags, f"'{keyword}' 키워드가 추가되었습니다."
|
| 95 |
-
else:
|
| 96 |
-
return current_tags, "이미 존재하는 키워드입니다."
|
| 97 |
-
|
| 98 |
-
def create_node(title, solution, tags):
|
| 99 |
-
"""사용자 입력으로 새 노드 생성"""
|
| 100 |
-
if not title or not solution:
|
| 101 |
-
return "프로젝트 제목과 솔루션을 모두 입력해주세요."
|
| 102 |
-
|
| 103 |
-
# 태그를 리스트로 변환
|
| 104 |
-
tags_list = [tag.strip() for tag in tags.split(',') if tag.strip()]
|
| 105 |
-
|
| 106 |
-
new_node = {
|
| 107 |
-
"title": title,
|
| 108 |
-
"solution": solution,
|
| 109 |
-
"tags": tags_list,
|
| 110 |
-
"source": "직접 입력"
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
nodes_data.append(new_node)
|
| 114 |
-
save_nodes()
|
| 115 |
-
|
| 116 |
-
return "새 노드가 성공적으로 생성되었습니다!"
|
| 117 |
-
|
| 118 |
-
# 3. 내 노드 확인하기 탭 함수들
|
| 119 |
-
def get_nodes_dataframe(filter_tag=""):
|
| 120 |
-
"""저장된 노드들을 데이터프레임으로 변환"""
|
| 121 |
-
if not nodes_data:
|
| 122 |
-
return pd.DataFrame()
|
| 123 |
-
|
| 124 |
-
# 필터링된 노드들
|
| 125 |
-
filtered_nodes = []
|
| 126 |
-
for node in nodes_data:
|
| 127 |
-
if not filter_tag or filter_tag in node.get('tags', []):
|
| 128 |
-
filtered_nodes.append({
|
| 129 |
-
"프로젝트 제목": node['title'],
|
| 130 |
-
"솔루션 소개": node['solution'][:100] + "..." if len(node['solution']) > 100 else node['solution'],
|
| 131 |
-
"태그": ', '.join(node.get('tags', [])),
|
| 132 |
-
"출처": node.get('source', '직접 입력')
|
| 133 |
-
})
|
| 134 |
-
|
| 135 |
-
return pd.DataFrame(filtered_nodes)
|
| 136 |
-
|
| 137 |
-
def filter_nodes(filter_tag):
|
| 138 |
-
"""태그로 노드 필터링"""
|
| 139 |
-
return get_nodes_dataframe(filter_tag)
|
| 140 |
-
|
| 141 |
-
def get_all_tags():
|
| 142 |
-
"""모든 노드의 태그 목록 반환"""
|
| 143 |
-
all_tags = set()
|
| 144 |
-
for node in nodes_data:
|
| 145 |
-
all_tags.update(node.get('tags', []))
|
| 146 |
-
return [""] + sorted(list(all_tags))
|
| 147 |
-
|
| 148 |
-
# 4. AI 아이디어 생성 탭 함수들
|
| 149 |
-
def add_team_member(member_code, current_members):
|
| 150 |
-
"""팀원 추가"""
|
| 151 |
-
if not member_code:
|
| 152 |
-
return current_members, "팀원 코드를 입력해주세요."
|
| 153 |
-
|
| 154 |
-
new_member = f"👤 {member_code} (닉네임)"
|
| 155 |
-
|
| 156 |
-
if current_members:
|
| 157 |
-
updated_members = current_members + f"\n{new_member}"
|
| 158 |
-
else:
|
| 159 |
-
updated_members = new_member
|
| 160 |
-
|
| 161 |
-
return updated_members, "팀원이 추가되었습니다!"
|
| 162 |
-
|
| 163 |
-
def generate_idea_chatgpt(competition_name, is_development, category, team_members):
|
| 164 |
-
"""ChatGPT로 아이디어 생성"""
|
| 165 |
-
if not competition_name:
|
| 166 |
-
return "공모전 이름을 입력해주세요."
|
| 167 |
-
|
| 168 |
-
idea_content = f"""## 🤖 ChatGPT 생성 아이디어
|
| 169 |
-
|
| 170 |
-
**공모전:** {competition_name}
|
| 171 |
-
**개발 여부:** {is_development}
|
| 172 |
-
**분야:** {category}
|
| 173 |
-
|
| 174 |
-
### 아이디어: "스마트 환경 모니터링 시스템"
|
| 175 |
-
|
| 176 |
-
**핵심 컨셉:**
|
| 177 |
-
당신의 노드에서 분석한 IoT와 데이터 분석 경험을 활용하여, 실시간 환경 데이터를 수집하고 예측 분석을 제공하는 시스템을 제안합니다.
|
| 178 |
-
|
| 179 |
-
**주요 기능:**
|
| 180 |
-
1. 다중 센서 기반 환경 데이터 수집
|
| 181 |
-
2. AI 기반 환경 변화 예측
|
| 182 |
-
3. 실시간 알림 및 대응 방안 제시
|
| 183 |
-
4. 커뮤니티 기반 환경 개선 참여 플랫폼
|
| 184 |
-
|
| 185 |
-
**활용된 노드:**
|
| 186 |
-
• 이전 IoT 프로젝트의 센서 활용 경험
|
| 187 |
-
• 데이터 분석 및 시각화 기술
|
| 188 |
-
• 웹/앱 개발 경험
|
| 189 |
-
|
| 190 |
-
**차별점:**
|
| 191 |
-
기존 환경 모니터링 시스템과 달리, 개인 사용자도 쉽게 참여할 수 있는 접근성과 AI 예측 기능을 결합"""
|
| 192 |
-
|
| 193 |
-
# 아이디어 저장
|
| 194 |
-
new_idea = {
|
| 195 |
-
"title": "스마트 환경 모니터링 시스템",
|
| 196 |
-
"competition": competition_name,
|
| 197 |
-
"generator": "ChatGPT",
|
| 198 |
-
"summary": "IoT와 데이터 분석을 활용한 실시간 환경 모니터링 및 예측 시스템",
|
| 199 |
-
"content": idea_content,
|
| 200 |
-
"category": category,
|
| 201 |
-
"development_type": is_development
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
ideas_data.append(new_idea)
|
| 205 |
-
save_ideas()
|
| 206 |
-
|
| 207 |
-
return idea_content
|
| 208 |
-
|
| 209 |
-
def generate_idea_gemini(competition_name, is_development, category, team_members):
|
| 210 |
-
"""Gemini로 아이디어 생성"""
|
| 211 |
-
if not competition_name:
|
| 212 |
-
return "공모전 이름을 입력해주세요."
|
| 213 |
-
|
| 214 |
-
idea_content = f"""## 🎯 Gemini 생성 아이디어
|
| 215 |
-
|
| 216 |
-
**공모전:** {competition_name}
|
| 217 |
-
**개발 여부:** {is_development}
|
| 218 |
-
**분야:** {category}
|
| 219 |
-
|
| 220 |
-
### 아이디어: "협업 기반 지역 문제 해결 플랫폼"
|
| 221 |
-
|
| 222 |
-
**핵심 컨셉:**
|
| 223 |
-
지역 주민들이 직면한 실질적 문제를 발굴하고, 다양한 배경의 사람들이 협업하여 해결책을 찾는 플랫폼입니다.
|
| 224 |
-
|
| 225 |
-
**주요 기능:**
|
| 226 |
-
1. 지역 문제 제보 및 검증 시스템
|
| 227 |
-
2. 스킬 기반 팀 매칭 알고리즘
|
| 228 |
-
3. 프로젝트 진행 관리 및 리소스 공유
|
| 229 |
-
4. 성과 측정 및 영향력 분석 도구
|
| 230 |
-
|
| 231 |
-
**활용된 노드:**
|
| 232 |
-
• 커뮤니티 플랫폼 개발 경험
|
| 233 |
-
• 사용자 경험 설계 역량
|
| 234 |
-
• 데이터 기반 의사결정 경험
|
| 235 |
-
|
| 236 |
-
**혁신성:**
|
| 237 |
-
단순한 아이디어 제안을 넘어서, 실제 실행 가능한 프로젝트로 연결하는 구조적 접근"""
|
| 238 |
-
|
| 239 |
-
# 아이디어 저장
|
| 240 |
-
new_idea = {
|
| 241 |
-
"title": "협업 기반 지역 문제 해결 플랫폼",
|
| 242 |
-
"competition": competition_name,
|
| 243 |
-
"generator": "Gemini",
|
| 244 |
-
"summary": "지역 주민과 다양한 배경의 사람들이 협업하여 실질적 문제를 해결하는 플랫폼",
|
| 245 |
-
"content": idea_content,
|
| 246 |
-
"category": category,
|
| 247 |
-
"development_type": is_development
|
| 248 |
-
}
|
| 249 |
-
|
| 250 |
-
ideas_data.append(new_idea)
|
| 251 |
-
save_ideas()
|
| 252 |
-
|
| 253 |
-
return idea_content
|
| 254 |
-
|
| 255 |
-
# 5. 생성된 아이디어 확인하기 탭 함수들
|
| 256 |
-
def get_ideas_display():
|
| 257 |
-
"""생성된 아이디어들을 표시용으로 변환"""
|
| 258 |
-
if not ideas_data:
|
| 259 |
-
return "아직 생성된 아이디어가 없습니다."
|
| 260 |
-
|
| 261 |
-
display_items = []
|
| 262 |
-
for i, idea in enumerate(ideas_data):
|
| 263 |
-
card = f"""
|
| 264 |
-
<div style="border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin: 8px 0; background: #f9f9f9;">
|
| 265 |
-
<h3>💡 {idea['title']}</h3>
|
| 266 |
-
<p><strong>생성 AI:</strong> {idea['generator']} | <strong>공모전:</strong> {idea['competition']}</p>
|
| 267 |
-
<p><strong>요약:</strong> {idea['summary']}</p>
|
| 268 |
-
<details>
|
| 269 |
-
<summary>자세한 내용 보기</summary>
|
| 270 |
-
<div style="margin-top: 12px; padding: 12px; background: white; border-radius: 4px;">
|
| 271 |
-
{idea['content'].replace('\n', '<br>')}
|
| 272 |
-
</div>
|
| 273 |
-
</details>
|
| 274 |
-
</div>
|
| 275 |
-
"""
|
| 276 |
-
display_items.append(card)
|
| 277 |
-
|
| 278 |
-
return "".join(display_items)
|
| 279 |
-
|
| 280 |
-
def refresh_ideas():
|
| 281 |
-
"""아이디어 목록 새로고침"""
|
| 282 |
-
load_ideas()
|
| 283 |
-
return get_ideas_display()
|
| 284 |
-
|
| 285 |
-
# 앱 시작 시 데이터 로드
|
| 286 |
-
load_nodes()
|
| 287 |
-
load_ideas()
|
| 288 |
|
| 289 |
# Gradio 인터페이스 구성
|
| 290 |
with gr.Blocks(title="노드폴리오", theme=gr.themes.Soft()) as app:
|
|
@@ -385,7 +115,7 @@ with gr.Blocks(title="노드폴리오", theme=gr.themes.Soft()) as app:
|
|
| 385 |
|
| 386 |
# 이벤트 연결
|
| 387 |
refresh_btn.click(
|
| 388 |
-
|
| 389 |
outputs=[nodes_dataframe, tag_filter]
|
| 390 |
)
|
| 391 |
|
|
|
|
| 3 |
import os
|
| 4 |
import pandas as pd
|
| 5 |
from typing import List, Dict, Any
|
| 6 |
+
from data_manager import initialize_data
|
| 7 |
+
from node_functions import (
|
| 8 |
+
upload_portfolio_files, process_uploaded_files, add_keyword, create_node,
|
| 9 |
+
get_nodes_dataframe, filter_nodes, get_all_tags, refresh_nodes
|
| 10 |
+
)
|
| 11 |
+
from idea_functions import (
|
| 12 |
+
add_team_member, generate_idea_chatgpt, generate_idea_gemini,
|
| 13 |
+
get_ideas_display, refresh_ideas
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# 앱 시작 시 데이터 초기화
|
| 17 |
+
initialize_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
# Gradio 인터페이스 구성
|
| 20 |
with gr.Blocks(title="노드폴리오", theme=gr.themes.Soft()) as app:
|
|
|
|
| 115 |
|
| 116 |
# 이벤트 연결
|
| 117 |
refresh_btn.click(
|
| 118 |
+
refresh_nodes,
|
| 119 |
outputs=[nodes_dataframe, tag_filter]
|
| 120 |
)
|
| 121 |
|
data_manager.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from typing import List, Dict, Any
|
| 4 |
+
|
| 5 |
+
# 전역 변수
|
| 6 |
+
nodes_data = []
|
| 7 |
+
ideas_data = []
|
| 8 |
+
|
| 9 |
+
def save_nodes():
|
| 10 |
+
"""노드 데이터를 JSON 파일로 저장"""
|
| 11 |
+
with open('nodes_data.json', 'w', encoding='utf-8') as f:
|
| 12 |
+
json.dump(nodes_data, f, ensure_ascii=False, indent=2)
|
| 13 |
+
|
| 14 |
+
def save_ideas():
|
| 15 |
+
"""아이디어 데이터를 JSON 파일로 저장"""
|
| 16 |
+
with open('ideas_data.json', 'w', encoding='utf-8') as f:
|
| 17 |
+
json.dump(ideas_data, f, ensure_ascii=False, indent=2)
|
| 18 |
+
|
| 19 |
+
def load_nodes():
|
| 20 |
+
"""저장된 노드 데이터 불러오기"""
|
| 21 |
+
global nodes_data
|
| 22 |
+
if os.path.exists('nodes_data.json'):
|
| 23 |
+
try:
|
| 24 |
+
with open('nodes_data.json', 'r', encoding='utf-8') as f:
|
| 25 |
+
nodes_data = json.load(f)
|
| 26 |
+
except (json.JSONDecodeError, ValueError):
|
| 27 |
+
nodes_data = []
|
| 28 |
+
print("nodes_data.json 파일이 손상되었거나 비어있습니다. 새로 시작합니다.")
|
| 29 |
+
else:
|
| 30 |
+
nodes_data = []
|
| 31 |
+
|
| 32 |
+
def load_ideas():
|
| 33 |
+
"""저장된 아이디어 데이터 불러오기"""
|
| 34 |
+
global ideas_data
|
| 35 |
+
if os.path.exists('ideas_data.json'):
|
| 36 |
+
try:
|
| 37 |
+
with open('ideas_data.json', 'r', encoding='utf-8') as f:
|
| 38 |
+
ideas_data = json.load(f)
|
| 39 |
+
except (json.JSONDecodeError, ValueError):
|
| 40 |
+
ideas_data = []
|
| 41 |
+
print("ideas_data.json 파일이 손상되었거나 비어있습니다. 새로 시작합니다.")
|
| 42 |
+
else:
|
| 43 |
+
ideas_data = []
|
| 44 |
+
|
| 45 |
+
def initialize_data():
|
| 46 |
+
"""앱 시작 시 데이터 초기화"""
|
| 47 |
+
load_nodes()
|
| 48 |
+
load_ideas()
|
idea_functions.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from typing import List, Dict, Any
|
| 4 |
+
|
| 5 |
+
# 전역 변수
|
| 6 |
+
nodes_data = []
|
| 7 |
+
ideas_data = []
|
| 8 |
+
|
| 9 |
+
def save_nodes():
|
| 10 |
+
"""노드 데이터를 JSON 파일로 저장"""
|
| 11 |
+
with open('nodes_data.json', 'w', encoding='utf-8') as f:
|
| 12 |
+
json.dump(nodes_data, f, ensure_ascii=False, indent=2)
|
| 13 |
+
|
| 14 |
+
def save_ideas():
|
| 15 |
+
"""아이디어 데이터를 JSON 파일로 저장"""
|
| 16 |
+
with open('ideas_data.json', 'w', encoding='utf-8') as f:
|
| 17 |
+
json.dump(ideas_data, f, ensure_ascii=False, indent=2)
|
| 18 |
+
|
| 19 |
+
def load_nodes():
|
| 20 |
+
"""저장된 노드 데이터 불러오기"""
|
| 21 |
+
global nodes_data
|
| 22 |
+
if os.path.exists('nodes_data.json'):
|
| 23 |
+
try:
|
| 24 |
+
with open('nodes_data.json', 'r', encoding='utf-8') as f:
|
| 25 |
+
nodes_data = json.load(f)
|
| 26 |
+
except (json.JSONDecodeError, ValueError):
|
| 27 |
+
nodes_data = []
|
| 28 |
+
print("nodes_data.json 파일이 손상되었거나 비어있습니다. 새로 시작합니다.")
|
| 29 |
+
else:
|
| 30 |
+
nodes_data = []
|
| 31 |
+
|
| 32 |
+
def load_ideas():
|
| 33 |
+
"""저장된 아이디어 데이터 불러오기"""
|
| 34 |
+
global ideas_data
|
| 35 |
+
if os.path.exists('ideas_data.json'):
|
| 36 |
+
try:
|
| 37 |
+
with open('ideas_data.json', 'r', encoding='utf-8') as f:
|
| 38 |
+
ideas_data = json.load(f)
|
| 39 |
+
except (json.JSONDecodeError, ValueError):
|
| 40 |
+
ideas_data = []
|
| 41 |
+
print("ideas_data.json 파일이 손상되었거나 비어있습니다. 새로 시작합니다.")
|
| 42 |
+
else:
|
| 43 |
+
ideas_data = []
|
| 44 |
+
|
| 45 |
+
def initialize_data():
|
| 46 |
+
"""앱 시작 시 데이터 초기화"""
|
| 47 |
+
load_nodes()
|
| 48 |
+
load_ideas()
|
node_functions.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
from data_manager import nodes_data, save_nodes
|
| 3 |
+
|
| 4 |
+
def upload_portfolio_files(files):
|
| 5 |
+
"""포트폴리오 파일 업로드 처리"""
|
| 6 |
+
if not files:
|
| 7 |
+
return "파일을 선택해주세요.", ""
|
| 8 |
+
|
| 9 |
+
uploaded_files = []
|
| 10 |
+
for file in files:
|
| 11 |
+
if file:
|
| 12 |
+
uploaded_files.append(f"📄 {file.name}")
|
| 13 |
+
|
| 14 |
+
files_display = "\n".join(uploaded_files)
|
| 15 |
+
return f"업로드된 파일:\n{files_display}", files_display
|
| 16 |
+
|
| 17 |
+
def process_uploaded_files(files_display):
|
| 18 |
+
"""업로드된 파일들을 AI로 분석하여 노드 생성"""
|
| 19 |
+
if not files_display:
|
| 20 |
+
return "업로드된 파일이 없습니다."
|
| 21 |
+
|
| 22 |
+
# TODO: 실제 AI 분석 로직 구현
|
| 23 |
+
sample_node = {
|
| 24 |
+
"title": "AI 분석된 프로젝트",
|
| 25 |
+
"solution": "업로드된 파일에서 분석된 솔루션과 핵심 기능들을 포함한 상세 설명",
|
| 26 |
+
"tags": ["AI", "데이터분석", "웹개발"],
|
| 27 |
+
"source": "파일 업로드"
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
nodes_data.append(sample_node)
|
| 31 |
+
save_nodes()
|
| 32 |
+
|
| 33 |
+
return "파일 분석이 완료되어 노드가 생성되었습니다!"
|
| 34 |
+
|
| 35 |
+
def add_keyword(keyword, current_tags):
|
| 36 |
+
"""키워드 추가"""
|
| 37 |
+
if not keyword:
|
| 38 |
+
return current_tags, "키워드를 입력해주세요."
|
| 39 |
+
|
| 40 |
+
if current_tags:
|
| 41 |
+
tags_list = [tag.strip() for tag in current_tags.split(',') if tag.strip()]
|
| 42 |
+
else:
|
| 43 |
+
tags_list = []
|
| 44 |
+
|
| 45 |
+
if keyword not in tags_list:
|
| 46 |
+
tags_list.append(keyword)
|
| 47 |
+
updated_tags = ', '.join(tags_list)
|
| 48 |
+
return updated_tags, f"'{keyword}' 키워드가 추가되었습니다."
|
| 49 |
+
else:
|
| 50 |
+
return current_tags, "이미 존재하는 키워드입니다."
|
| 51 |
+
|
| 52 |
+
def create_node(title, solution, tags):
|
| 53 |
+
"""사용자 입력으로 새 노드 생성"""
|
| 54 |
+
if not title or not solution:
|
| 55 |
+
return "프로젝트 제목과 솔루션을 모두 입력해주세요."
|
| 56 |
+
|
| 57 |
+
# 태그를 리스트로 변환
|
| 58 |
+
tags_list = [tag.strip() for tag in tags.split(',') if tag.strip()]
|
| 59 |
+
|
| 60 |
+
new_node = {
|
| 61 |
+
"title": title,
|
| 62 |
+
"solution": solution,
|
| 63 |
+
"tags": tags_list,
|
| 64 |
+
"source": "직접 입력"
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
nodes_data.append(new_node)
|
| 68 |
+
save_nodes()
|
| 69 |
+
|
| 70 |
+
return "새 노드가 성공적으로 생성되었습니다!"
|
| 71 |
+
|
| 72 |
+
def get_nodes_dataframe(filter_tag=""):
|
| 73 |
+
"""저장된 노드들을 데이터프레임으로 변환"""
|
| 74 |
+
if not nodes_data:
|
| 75 |
+
return pd.DataFrame()
|
| 76 |
+
|
| 77 |
+
# 필터링된 노드들
|
| 78 |
+
filtered_nodes = []
|
| 79 |
+
for node in nodes_data:
|
| 80 |
+
if not filter_tag or filter_tag in node.get('tags', []):
|
| 81 |
+
filtered_nodes.append({
|
| 82 |
+
"프로젝트 제목": node['title'],
|
| 83 |
+
"솔루션 소개": node['solution'][:100] + "..." if len(node['solution']) > 100 else node['solution'],
|
| 84 |
+
"태그": ', '.join(node.get('tags', [])),
|
| 85 |
+
"출처": node.get('source', '직접 입력')
|
| 86 |
+
})
|
| 87 |
+
|
| 88 |
+
return pd.DataFrame(filtered_nodes)
|
| 89 |
+
|
| 90 |
+
def filter_nodes(filter_tag):
|
| 91 |
+
"""태그로 노드 필터링"""
|
| 92 |
+
return get_nodes_dataframe(filter_tag)
|
| 93 |
+
|
| 94 |
+
def get_all_tags():
|
| 95 |
+
"""모든 노드의 태그 목록 반환"""
|
| 96 |
+
all_tags = set()
|
| 97 |
+
for node in nodes_data:
|
| 98 |
+
all_tags.update(node.get('tags', []))
|
| 99 |
+
return [""] + sorted(list(all_tags))
|
| 100 |
+
|
| 101 |
+
def refresh_nodes():
|
| 102 |
+
"""노드 목록 새로고침"""
|
| 103 |
+
from data_manager import load_nodes
|
| 104 |
+
load_nodes()
|
| 105 |
+
return get_nodes_dataframe(), get_all_tags()
|