FineTLLM / app.py
jeongkee's picture
Upload app.py
f1d1d29 verified
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
G-Mission Foundation Model Partnership Dashboard
Hugging Face Space 배포 최적화 버전
"""
import os
import subprocess
import sys
import datetime
import traceback
import gc
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import ssl
import re
# 패키지 설치 함수
def install_packages():
packages = ['gradio>=4.0.0', 'plotly>=5.0.0', 'pandas>=2.0.0', 'openpyxl>=3.0.0']
for package in packages:
try:
pkg_name = package.split('>=')[0]
__import__(pkg_name)
except ImportError:
print(f"Installing {package}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", package, "--quiet"])
# 패키지 설치 실행
install_packages()
import pandas as pd
import gradio as gr
try:
import plotly.express as px
import plotly.graph_objects as go
PLOTLY_AVAILABLE = True
except ImportError:
PLOTLY_AVAILABLE = False
from typing import Tuple, List, Dict, Any, Optional
# ==================== 설정 ====================
PASTEL_COLORS = {
'primary_palette': ['#FFB3BA', '#FFDFBA', '#FFFFBA', '#BAFFC9', '#BAE1FF', '#E5BAFF', '#FFBAE5'],
'secondary_palette': ['#FFD6D6', '#FFEBD6', '#FFFFD6', '#D6FFE0', '#D6EBFF', '#E0D6FF', '#FFD6EB'],
'value_chain_colors': {
'LLM': '#BAFFC9',
'AI Infra': '#FFB3BA',
'경량AI': '#BAE1FF',
'Agentic': '#FFDFBA',
'Platform': '#E5BAFF',
'전체': '#FFFFBA'
},
'maturity_colors': {
'성숙': '#BAFFC9',
'중견': '#BAE1FF',
'스타트업': '#FFDFBA',
'초기': '#FFB3BA'
}
}
VALUE_CHAIN_CATEGORIES = ["전체"]
# ==================== Gmail SMTP 시스템 ====================
class GmailEmailSystem:
def __init__(self):
self.email_history = []
self.smtp_server = "smtp.gmail.com"
self.port = 587
# Hugging Face Secrets에서 설정 로드
self.gmail_email = os.getenv("GMAIL_EMAIL", "jeongkee1967.kim@gmail.com")
self.gmail_password = os.getenv("GMAIL_PASSWORD", "")
self.recipient_email = os.getenv("RECIPIENT_EMAIL", "jeongkee10@gmission.co.kr")
# 설정 상태 확인
self.is_configured = bool(self.gmail_password)
if self.is_configured:
print(f"✅ Gmail SMTP 설정 완료: {self.gmail_email}")
else:
print("⚠️ Gmail SMTP 미설정 - Secrets에 GMAIL_PASSWORD 추가 필요")
def validate_email(self, email: str) -> bool:
if pd.isna(email) or str(email).strip() in ['', 'nan', 'NaN', 'N/A']:
return False
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, str(email)) is not None
def send_email(self, subject: str, body: str, company_name: str, target_email: str) -> Tuple[bool, str]:
try:
if not self.is_configured:
return False, """⚠️ Gmail SMTP가 설정되지 않았습니다.
**설정 방법:**
1. Gmail 앱 비밀번호 생성:
- https://myaccount.google.com/apppasswords
- 앱: 메일, 기기: 기타 (G-Mission Dashboard)
- 16자리 비밀번호 복사
2. Hugging Face Space Secrets 설정:
- Space Settings → Secrets
- 다음 3개 변수 추가:
* GMAIL_EMAIL = jeongkee1967.kim@gmail.com
* GMAIL_PASSWORD = [16자리 앱 비밀번호]
* RECIPIENT_EMAIL = jeongkee10@gmission.co.kr
3. Space 재시작:
- Settings → Factory Reboot
"""
if not self.validate_email(target_email):
return False, f"❌ 유효하지 않은 이메일: {target_email}"
# 이메일 메시지 생성
message = MIMEMultipart()
message["From"] = f"G-Mission Partnership <{self.gmail_email}>"
message["To"] = self.recipient_email
message["Subject"] = f"[Partnership Proposal] {subject}"
enhanced_body = f"""
<html>
<head>
<style>
body {{
font-family: 'Times New Roman', Times, serif;
font-size: 14px;
line-height: 1.6;
color: #333;
}}
.header {{
font-weight: bold;
margin-top: 20px;
margin-bottom: 10px;
}}
.divider {{
border-top: 1px solid #ccc;
margin: 20px 0;
}}
</style>
</head>
<body>
<p>G-Mission Partnership Team,</p>
<p>Strategic partnership proposal generated:</p>
<div class="divider"></div>
<p class="header">TARGET COMPANY</p>
<p>
<strong>Company:</strong> {company_name}<br>
<strong>Email:</strong> {target_email}<br>
<strong>Generated:</strong> {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S KST')}
</p>
<div class="divider"></div>
<p class="header">PROPOSAL CONTENT</p>
<div style="white-space: pre-wrap;">{body}</div>
<div class="divider"></div>
<p class="header">NEXT STEPS</p>
<ol>
<li>Review and customize content</li>
<li>Send to: {target_email}</li>
<li>Track response</li>
<li>Schedule follow-up meeting</li>
</ol>
<div class="divider"></div>
<p style="font-size: 12px; color: #666;">
Generated by G-Mission Partnership Dashboard<br>
<a href="https://huggingface.co/spaces/[your-space-name]">https://huggingface.co/spaces/[your-space-name]</a>
</p>
</body>
</html>
"""
message.attach(MIMEText(enhanced_body, "html", "utf-8"))
# Gmail SMTP 발송
context = ssl.create_default_context()
with smtplib.SMTP(self.smtp_server, self.port) as server:
server.starttls(context=context)
server.login(self.gmail_email, self.gmail_password)
server.sendmail(self.gmail_email, self.recipient_email, message.as_string())
self.record_email_send(company_name, target_email, subject, body[:100])
return True, f"✅ 이메일 발송 성공!\n\n📧 발신: {self.gmail_email}\n📧 수신: {self.recipient_email}\n🏢 대상: {company_name}"
except smtplib.SMTPAuthenticationError:
return False, """❌ Gmail 인증 실패
**해결 방법:**
1. Gmail 2단계 인증 활성화 확인
2. 앱 비밀번호 재생성
3. Hugging Face Secrets에 정확한 비밀번호 입력
4. Space 재시작
참고: 일반 비밀번호가 아닌 '앱 비밀번호' 필요"""
except Exception as e:
return False, f"❌ 발송 실패: {str(e)}"
def create_partnership_email(self, company_name: str, email: str, value_chain: str,
maturity: str = "", country: str = "", website: str = "",
primary_business: str = "", target_customer: str = "",
opensource_type: str = "", rag_agentic: str = "") -> Tuple[str, str, str]:
try:
if not self.validate_email(email):
return "", "", ""
subject = f"Strategic AI Partnership: {company_name} × G-Mission"
# G-Mission 핵심 강점
gmission_core = """
G-MISSION: ASIAN MARKET LEADER IN ENTERPRISE AI
Core Strengths:
• 500+ Enterprise Customers: Government, financial, and corporate sectors in Korea
• AI Solutions: AI FAX, Vision AI, Document Automation, Agentic AI
• Asian Network: Strong presence in Japan, Indonesia, and APAC region
• Technical Excellence: LLM fine-tuning, AI pipeline framework, open-source optimization
"""
# 파트너십 모델
partnership_models = """
PARTNERSHIP MODELS:
1. White-Label Partnership
- G-Mission branded distribution in Asian markets
- Revenue sharing based on regional performance
2. Joint Development
- Co-develop industry solutions
- Shared IP and go-to-market strategy
3. Technology Integration & OEM
- Integration with G-Mission's Agentic AI platform
- OEM licensing for enterprise solutions
4. Market Expansion Alliance
- G-Mission as preferred partner for Korea/APAC
- Joint sales leveraging 500+ customer network
5. R&D Collaboration
- Joint research initiatives
- Shared access to enterprise use cases
"""
# Next Steps
next_steps = """
PROPOSED NEXT STEPS:
1. Initial Partnership Discussion (Virtual)
- Overview of capabilities and alignment
- Discussion of partnership models
2. Technical Deep-Dive
- Integration assessment
- Architecture and deployment planning
3. Business Terms
- Partnership structure and revenue sharing
- Geographic territories
4. Pilot Project
- 2-3 enterprise customer deployments
- Success metrics and monitoring
5. Full Partnership Agreement
- Comprehensive contract
- Joint business plan and governance
"""
body = f"""Dear {company_name} Partnership Team,
We would like to propose a strategic partnership leveraging our complementary strengths in the Asian AI market.
{gmission_core}
VALUE PROPOSITION:
Why Partner with G-Mission:
• Immediate access to 500+ enterprise customers
• Proven Asian market expertise and relationships
• Technical integration and fine-tuning capabilities
• Reduced time-to-market with established infrastructure
• Revenue growth through existing sales pipeline
{partnership_models}
{next_steps}
MEETING REQUEST:
We would be honored to schedule a partnership discussion at your convenience. Our team can present:
• Market analysis and opportunity assessment
• Technical integration roadmap
• Partnership framework and commercial terms
• Customer case studies
Would your team be available for a virtual meeting in the next two weeks?
Best regards,
Jeong Kee Kim
Chief Partnership Officer
G-Mission Corporation
Email: jeongkee1967.kim@gmail.com
Website: https://www.gmission.com
---
Generated: {datetime.datetime.now().strftime('%B %d, %Y')}
Company Stage: {maturity if maturity else 'Growth-stage'}
Partnership Focus: {value_chain if value_chain else 'Strategic Alliance'}
"""
return subject, body, str(email)
except Exception as e:
print(f"이메일 생성 오류: {e}")
return "", "", ""
def record_email_send(self, company_name: str, email: str, subject: str, body_preview: str) -> bool:
try:
self.email_history.append({
'timestamp': datetime.datetime.now(),
'company_name': str(company_name),
'target_email': str(email),
'subject': str(subject),
'body_preview': str(body_preview),
'recipient': self.recipient_email,
'status': '발송 완료',
'send_date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
})
return True
except:
return False
def get_email_history_text(self) -> str:
if not self.email_history:
config_status = "✅ 설정됨" if self.is_configured else "❌ 미설정"
return f"""📧 이메일 발송 현황
**Gmail SMTP 상태:** {config_status}
**발신 이메일:** {self.gmail_email}
**수신 이메일:** {self.recipient_email}
아직 발송된 이메일이 없습니다.
**사용 방법:**
1. 기업 분석 실행
2. 이메일 탭에서 내용 확인
3. '이메일 발송' 버튼 클릭
"""
history_text = f"📧 이메일 발송 현황 (총 {len(self.email_history)}건)\n\n"
for i, record in enumerate(reversed(self.email_history), 1):
history_text += f"""
**{i}. {record['company_name']}**
- 발송: {record['send_date']}
- 대상: {record['target_email']}
- 수신: {record['recipient']}
- 상태: ✅ {record['status']}
---
"""
return history_text
# ==================== 데이터 처리 ====================
class OptimizedDataProcessor:
@staticmethod
def process_data(df: pd.DataFrame) -> pd.DataFrame:
try:
if df is None or df.empty:
return pd.DataFrame()
df.columns = df.columns.str.strip()
required_columns = ['회사명', 'Value Chain']
for col in required_columns:
if col not in df.columns:
print(f"⚠️ 필수 컬럼 누락: {col}")
return pd.DataFrame()
processed_df = df.copy().fillna('정보 없음')
if 'Value Chain' in processed_df.columns:
processed_df['Value Chain'] = processed_df['Value Chain'].astype(str).str.strip()
# 메모리 최적화
for col in processed_df.columns:
if processed_df[col].dtype == 'object':
unique_ratio = len(processed_df[col].unique()) / len(processed_df)
if unique_ratio < 0.5:
processed_df[col] = processed_df[col].astype('category')
return processed_df
except Exception as e:
print(f"데이터 처리 오류: {e}")
return df if df is not None else pd.DataFrame()
# ==================== 차트 생성 ====================
class PastelChartGenerator:
@staticmethod
def create_value_chain_optimized_charts(df: pd.DataFrame, value_chain_filter: str = "전체") -> Tuple[Any, Any, Any, Any, str]:
try:
if not PLOTLY_AVAILABLE or df is None or df.empty:
return None, None, None, None, "데이터가 없습니다."
if value_chain_filter != "전체":
filtered_df = df[df['Value Chain'] == value_chain_filter].copy()
else:
filtered_df = df.copy()
if filtered_df.empty:
return None, None, None, None, f"{value_chain_filter} 데이터가 없습니다."
total_companies = len(filtered_df)
mature_companies = len(filtered_df[filtered_df['성장단계'] == '성숙']) if '성장단계' in filtered_df.columns else 0
countries = filtered_df['국가/지역'].nunique() if '국가/지역' in filtered_df.columns else 0
summary_text = f"""### 📊 분석 요약
**총 기업 수:** {total_companies}
**성숙 단계:** {mature_companies}
**참여 국가:** {countries}개국"""
# 차트 1: Value Chain 분포
if value_chain_filter == "전체" and 'Value Chain' in filtered_df.columns:
vc_counts = filtered_df['Value Chain'].value_counts()
fig1 = px.pie(values=vc_counts.values, names=vc_counts.index,
title=f"Value Chain 분포 ({total_companies}개)",
color_discrete_sequence=PASTEL_COLORS['primary_palette'])
else:
fig1 = go.Figure(data=[go.Pie(labels=[value_chain_filter], values=[total_companies])])
fig1.update_layout(title=f"{value_chain_filter} ({total_companies}개)")
fig1.update_layout(height=400)
# 차트 2: 성장단계
if '성장단계' in filtered_df.columns:
maturity_counts = filtered_df['성장단계'].value_counts()
if not maturity_counts.empty:
fig2 = go.Figure(data=[go.Bar(x=maturity_counts.index, y=maturity_counts.values,
marker_color=PASTEL_COLORS['primary_palette'])])
fig2.update_layout(title="성장단계별 분포", height=400)
else:
fig2 = go.Figure()
else:
fig2 = go.Figure()
# 차트 3: 국가별
fig3 = go.Figure()
if '국가/지역' in filtered_df.columns:
country_counts = filtered_df['국가/지역'].value_counts().head(10)
if not country_counts.empty:
fig3 = px.bar(x=country_counts.values, y=country_counts.index, orientation='h',
title="국가별 분포", color_discrete_sequence=PASTEL_COLORS['primary_palette'])
fig3.update_layout(height=400)
# 차트 4: 오픈소스
fig4 = go.Figure()
if '오픈소스/상용' in filtered_df.columns:
oss_counts = filtered_df['오픈소스/상용'].value_counts()
if not oss_counts.empty:
fig4 = px.pie(values=oss_counts.values, names=oss_counts.index,
title="오픈소스/상용 분포",
color_discrete_sequence=PASTEL_COLORS['secondary_palette'])
fig4.update_layout(height=400)
return fig1, fig2, fig3, fig4, summary_text
except Exception as e:
print(f"차트 생성 오류: {e}")
return None, None, None, None, f"오류: {str(e)}"
# ==================== 기업 분석 ====================
def analyze_company(df: pd.DataFrame, company_name: str, email_system: GmailEmailSystem) -> Tuple[str, str, str, str]:
try:
if df is None or df.empty or not company_name:
return "기업을 선택해주세요.", "", "", ""
company_data = df[df['회사명'] == company_name]
if company_data.empty:
return f"'{company_name}' 기업을 찾을 수 없습니다.", "", "", ""
data = company_data.iloc[0]
def safe_get(field, default='정보 없음'):
value = data.get(field, default)
if pd.isna(value) or str(value).strip() in ['', 'nan', 'NaN', 'N/A']:
return default
return str(value)
company_name_clean = safe_get('회사명')
value_chain = safe_get('Value Chain')
maturity = safe_get('성장단계')
email_address = safe_get('이메일')
country = safe_get('국가/지역')
website = safe_get('공식URL')
analysis = f"""# 🏢 {company_name_clean} 분석
## 기본 정보
- **Value Chain:** {value_chain}
- **성장 단계:** {maturity}
- **국가:** {country}
- **웹사이트:** {website}
- **이메일:** {email_address}
## 기술 정보
- **주요사업:** {safe_get('주요사업')}
- **모델/플랫폼:** {safe_get('파운데이션/특화모델')}
- **배포방식:** {safe_get('배포방식')}
- **오픈소스:** {safe_get('오픈소스/상용')}
## 비즈니스
- **주요고객:** {safe_get('주요고객타겟')}
- **산업도메인:** {safe_get('주력산업도메인')}
- **비즈니스모델:** {safe_get('라이선스/비즈니스모델')}
## G-Mission 파트너십 제안
**협력 방향:**
- 500+ 고객 네트워크 활용
- 아시아 시장 공동 진출
- 기술 통합 및 공동 개발
- White-label 또는 OEM 파트너십
📧 이메일 탭에서 맞춤형 파트너십 제안서를 확인하세요."""
if email_system.validate_email(email_address):
subject, body, recipient = email_system.create_partnership_email(
company_name_clean, email_address, value_chain, maturity, country, website,
safe_get('주요사업'), safe_get('주요고객타겟'),
safe_get('오픈소스/상용'), safe_get('RAG/Agentic')
)
return analysis, subject, body, recipient
else:
return analysis, "", "", ""
except Exception as e:
return f"분석 오류: {str(e)}", "", "", ""
# ==================== 파일 처리 ====================
def process_uploaded_file(file):
if file is None:
return None, "파일을 업로드해주세요."
try:
file_path = file.name if hasattr(file, 'name') else str(file)
if file_path.endswith('.csv'):
df = pd.read_csv(file_path, encoding='utf-8-sig')
elif file_path.endswith(('.xlsx', '.xls')):
df = pd.read_excel(file_path)
else:
return None, "CSV 또는 Excel 파일만 지원합니다."
if df.empty:
return None, "빈 파일입니다."
processed_df = OptimizedDataProcessor.process_data(df)
if processed_df.empty:
return None, "데이터 처리 실패."
success_msg = f"""✅ 파일 처리 완료!
- 총 기업: {len(processed_df)}
- 컬럼: {len(processed_df.columns)}
- Value Chain: {processed_df['Value Chain'].nunique() if 'Value Chain' in processed_df.columns else 0}개"""
del df
gc.collect()
return processed_df, success_msg
except Exception as e:
return None, f"오류: {str(e)}"
# ==================== 메인 대시보드 ====================
def create_dashboard():
email_system = GmailEmailSystem()
with gr.Blocks(title="G-Mission Partnership Dashboard", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🚀 G-Mission Foundation Model Partnership Dashboard
**500+ 고객 네트워크 기반 전략적 파트너십 분석 시스템**
- AI FAX, Vision AI, Agentic AI 솔루션 보유
- 일본, 인도네시아 등 아시아 시장 강력한 네트워크
- LLM Fine-tuning 및 AI Pipeline Framework 전문성
""")
df_state = gr.State(value=None)
with gr.Row():
with gr.Column():
file_upload = gr.File(label="📁 데이터 업로드", file_types=['.csv', '.xlsx', '.xls'])
upload_status = gr.Markdown("파일을 업로드해주세요.")
with gr.Tabs():
with gr.TabItem("📊 시장 분석"):
with gr.Row():
with gr.Column(scale=1):
market_filter = gr.Dropdown(choices=["전체"], value="전체", label="Value Chain 필터")
market_btn = gr.Button("분석 실행", variant="primary", size="lg")
market_summary = gr.Markdown("분석을 실행하세요.")
with gr.Row():
with gr.Column():
chart1 = gr.Plot(label="Value Chain 분포")
with gr.Column():
chart2 = gr.Plot(label="성장단계")
with gr.Row():
with gr.Column():
chart3 = gr.Plot(label="국가별 분포")
with gr.Column():
chart4 = gr.Plot(label="오픈소스/상용")
with gr.TabItem("🏢 기업 분석"):
with gr.Row():
with gr.Column(scale=1):
company_filter = gr.Dropdown(choices=["전체"], value="전체", label="Value Chain 필터")
company_dropdown = gr.Dropdown(choices=[], label="기업 선택")
analyze_btn = gr.Button("분석 실행", variant="primary", size="lg")
with gr.Column(scale=2):
company_analysis = gr.Markdown("기업을 선택하고 분석하세요.")
with gr.TabItem("📧 이메일"):
gr.Markdown(f"""
## Gmail SMTP 이메일 시스템
**현재 상태:** {'✅ 설정됨' if email_system.is_configured else '❌ 미설정'}
**설정 방법:**
1. Gmail 앱 비밀번호 생성: https://myaccount.google.com/apppasswords
2. Hugging Face Space Settings → Secrets 추가:
- GMAIL_EMAIL = jeongkee1967.kim@gmail.com
- GMAIL_PASSWORD = [16자리 앱 비밀번호]
- RECIPIENT_EMAIL = jeongkee10@gmission.co.kr
3. Space 재시작
""")
email_recipient = gr.Textbox(label="대상 이메일", placeholder="기업 분석 후 자동 입력")
email_subject = gr.Textbox(label="제목", placeholder="자동 생성됨")
email_body = gr.Textbox(label="내용", lines=15, placeholder="자동 생성됨")
send_btn = gr.Button("📧 이메일 발송", variant="primary", size="lg")
send_status = gr.Markdown("기업 분석 후 이메일이 생성됩니다.")
with gr.TabItem("📋 발송 현황"):
refresh_btn = gr.Button("🔄 새로고침", variant="primary")
history_display = gr.Markdown(email_system.get_email_history_text())
# 이벤트 핸들러
def handle_upload(file):
try:
df, status = process_uploaded_file(file)
if df is not None:
companies = sorted(df['회사명'].unique().tolist())
value_chains = ['전체'] + sorted(df['Value Chain'].unique().tolist()) if 'Value Chain' in df.columns else ['전체']
return (df, status,
gr.update(choices=companies, value=None),
gr.update(choices=value_chains, value="전체"),
gr.update(choices=value_chains, value="전체"))
else:
return None, status, gr.update(choices=[]), gr.update(choices=['전체']), gr.update(choices=['전체'])
except Exception as e:
return None, f"오류: {e}", gr.update(choices=[]), gr.update(choices=['전체']), gr.update(choices=['전체'])
file_upload.upload(
fn=handle_upload,
inputs=[file_upload],
outputs=[df_state, upload_status, company_dropdown, market_filter, company_filter]
)
market_btn.click(
fn=lambda df, f: PastelChartGenerator.create_value_chain_optimized_charts(df, f) if df is not None else (None,None,None,None,"데이터 없음"),
inputs=[df_state, market_filter],
outputs=[chart1, chart2, chart3, chart4, market_summary]
)
def update_companies(df, filter_val):
if df is None or df.empty:
return gr.update(choices=[])
filtered = df if filter_val == "전체" else df[df['Value Chain'] == filter_val]
return gr.update(choices=sorted(filtered['회사명'].unique().tolist()), value=None)
company_filter.change(fn=update_companies, inputs=[df_state, company_filter], outputs=[company_dropdown])
analyze_btn.click(
fn=lambda df, c: analyze_company(df, c, email_system),
inputs=[df_state, company_dropdown],
outputs=[company_analysis, email_subject, email_body, email_recipient]
)
def handle_send(recipient, subject, body):
if not email_system.validate_email(recipient):
return "유효한 이메일 주소가 필요합니다."
if not subject or not body:
return "제목과 내용이 필요합니다."
company = recipient.split('@')[0].capitalize()
success, msg = email_system.send_email(subject, body, company, recipient)
return msg
send_btn.click(fn=handle_send, inputs=[email_recipient, email_subject, email_body], outputs=[send_status])
refresh_btn.click(fn=lambda: email_system.get_email_history_text(), outputs=[history_display])
return demo
# ==================== 실행 ====================
if __name__ == "__main__":
print("🚀 G-Mission Partnership Dashboard 시작")
print("✅ Hugging Face Space 배포 최적화")
try:
demo = create_dashboard()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
show_error=True
)
print("✅ 대시보드 실행 성공")
except Exception as e:
print(f"❌ 실행 오류: {e}")
traceback.print_exc()