| | """ |
| | AI κΈ°λ° μκΆ λΆμ μμ€ν
- Comic Classic Theme λ²μ |
| | Dataset: https://huggingface.co/datasets/ginipick/market |
| | """ |
| | import gradio as gr |
| | import pandas as pd |
| | import numpy as np |
| | from typing import Dict, List, Tuple |
| | import json |
| | from datasets import load_dataset |
| | import plotly.express as px |
| | import plotly.graph_objects as go |
| | from plotly.subplots import make_subplots |
| | import folium |
| | from folium.plugins import HeatMap, MarkerCluster |
| | import requests |
| | from collections import Counter |
| | import re |
| | import os |
| | import time |
| |
|
| | |
| | |
| | |
| |
|
| | class BraveSearchClient: |
| | """Brave Search API ν΄λΌμ΄μΈνΈ""" |
| | |
| | def __init__(self, api_key: str = None): |
| | self.api_key = api_key or os.getenv("BRAVE_API_KEY") |
| | self.base_url = "https://api.search.brave.com/res/v1/web/search" |
| | |
| | def search(self, query: str, count: int = 5) -> str: |
| | """μΉ κ²μ μν""" |
| | if not self.api_key: |
| | return "β οΈ Brave Search API ν€κ° μ€μ λμ§ μμμ΅λλ€." |
| | |
| | headers = { |
| | "Accept": "application/json", |
| | "X-Subscription-Token": self.api_key |
| | } |
| | |
| | params = { |
| | "q": query, |
| | "count": count, |
| | "text_decorations": False, |
| | "search_lang": "ko" |
| | } |
| | |
| | try: |
| | response = requests.get(self.base_url, headers=headers, params=params, timeout=10) |
| | if response.status_code == 200: |
| | data = response.json() |
| | results = [] |
| | |
| | if 'web' in data and 'results' in data['web']: |
| | for item in data['web']['results'][:count]: |
| | title = item.get('title', '') |
| | description = item.get('description', '') |
| | url = item.get('url', '') |
| | results.append(f"π **{title}**\n{description}\nπ {url}") |
| | |
| | return "\n\n".join(results) if results else "κ²μ κ²°κ³Όκ° μμ΅λλ€." |
| | else: |
| | return f"β οΈ κ²μ μ€ν¨: {response.status_code}" |
| | except Exception as e: |
| | return f"β οΈ κ²μ μ€λ₯: {str(e)}" |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | class MarketDataLoader: |
| | """νκΉ
νμ΄μ€ μκΆ λ°μ΄ν° λ‘λ""" |
| | |
| | REGIONS = { |
| | 'μμΈ': 'μμΈ_202506', 'κ²½κΈ°': 'κ²½κΈ°_202506', 'λΆμ°': 'λΆμ°_202506', |
| | 'λꡬ': 'λꡬ_202506', 'μΈμ²': 'μΈμ²_202506', 'κ΄μ£Ό': 'κ΄μ£Ό_202506', |
| | 'λμ ': 'λμ _202506', 'μΈμ°': 'μΈμ°_202506', 'μΈμ’
': 'μΈμ’
_202506', |
| | 'κ²½λ¨': 'κ²½λ¨_202506', 'κ²½λΆ': 'κ²½λΆ_202506', 'μ λ¨': 'μ λ¨_202506', |
| | 'μ λΆ': 'μ λΆ_202506', 'μΆ©λ¨': 'μΆ©λ¨_202506', 'μΆ©λΆ': 'μΆ©λΆ_202506', |
| | 'κ°μ': 'κ°μ_202506', 'μ μ£Ό': 'μ μ£Ό_202506' |
| | } |
| | |
| | |
| | CATEGORY_MAPPING = { |
| | 'G2': 'μλ§€μ
', |
| | 'I1': 'μλ°μ
', |
| | 'I2': 'μμμ μ
', |
| | 'L1': 'λΆλμ°μ
', |
| | 'M1': 'μ λ¬Έ/κ³Όν/κΈ°μ ', |
| | 'N1': 'μ¬μ
μ§μ/μλ', |
| | 'P1': 'κ΅μ‘μλΉμ€', |
| | 'Q1': '보건μλ£', |
| | 'R1': 'μμ /μ€ν¬μΈ /μ¬κ°', |
| | 'S2': 'μ리/κ°μΈμλΉμ€' |
| | } |
| | |
| | @staticmethod |
| | def load_region_data(region: str, sample_size: int = 30000) -> pd.DataFrame: |
| | """μ§μλ³ λ°μ΄ν° λ‘λ""" |
| | try: |
| | file_name = f"μμ곡μΈμμ₯μ§ν₯곡λ¨_μκ°(μκΆ)μ 보_{MarketDataLoader.REGIONS[region]}.csv" |
| | dataset = load_dataset("ginipick/market", data_files=file_name, split="train") |
| | df = dataset.to_pandas() |
| | |
| | if len(df) > sample_size: |
| | df = df.sample(n=sample_size, random_state=42) |
| | |
| | return df |
| | except Exception as e: |
| | print(f"λ°μ΄ν° λ‘λ μ€ν¨: {str(e)}") |
| | return pd.DataFrame() |
| | |
| | @staticmethod |
| | def load_multiple_regions(regions: List[str], sample_per_region: int = 30000) -> pd.DataFrame: |
| | """μ¬λ¬ μ§μ λ°μ΄ν° λ‘λ""" |
| | dfs = [] |
| | for region in regions: |
| | df = MarketDataLoader.load_region_data(region, sample_per_region) |
| | if not df.empty: |
| | dfs.append(df) |
| | |
| | if dfs: |
| | return pd.concat(dfs, ignore_index=True) |
| | return pd.DataFrame() |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | class MarketAnalyzer: |
| | """μκΆ λ°μ΄ν° λΆμ μμ§""" |
| | |
| | def __init__(self, df: pd.DataFrame): |
| | self.df = df |
| | self.prepare_data() |
| | |
| | def prepare_data(self): |
| | """λ°μ΄ν° μ μ²λ¦¬""" |
| | if 'κ²½λ' in self.df.columns: |
| | self.df['κ²½λ'] = pd.to_numeric(self.df['κ²½λ'], errors='coerce') |
| | if 'μλ' in self.df.columns: |
| | self.df['μλ'] = pd.to_numeric(self.df['μλ'], errors='coerce') |
| | self.df = self.df.dropna(subset=['κ²½λ', 'μλ']) |
| | |
| | |
| | if 'μΈ΅μ 보' in self.df.columns: |
| | self.df['μΈ΅μ 보_μ«μ'] = self.df['μΈ΅μ 보'].apply(self._parse_floor) |
| | |
| | def _parse_floor(self, floor_str): |
| | """μΈ΅ μ 보λ₯Ό μ«μλ‘ λ³ν""" |
| | if pd.isna(floor_str): |
| | return None |
| | floor_str = str(floor_str) |
| | if 'μ§ν' in floor_str or 'B' in floor_str: |
| | match = re.search(r'\d+', floor_str) |
| | return -int(match.group()) if match else -1 |
| | elif '1μΈ΅' in floor_str or floor_str == '1': |
| | return 1 |
| | else: |
| | match = re.search(r'\d+', floor_str) |
| | return int(match.group()) if match else None |
| | |
| | def get_comprehensive_insights(self) -> List[Dict]: |
| | """ν¬κ΄μ μΈ μΈμ¬μ΄νΈ μμ±""" |
| | insights = [] |
| | |
| | insights.append(self._create_top_categories_chart()) |
| | insights.append(self._create_major_category_pie()) |
| | insights.append(self._create_floor_analysis()) |
| | insights.append(self._create_diversity_index()) |
| | insights.append(self._create_franchise_analysis()) |
| | insights.append(self._create_floor_preference()) |
| | insights.append(self._create_district_density()) |
| | insights.append(self._create_category_correlation()) |
| | insights.append(self._create_subcategory_trends()) |
| | insights.append(self._create_regional_specialization()) |
| | |
| | return insights |
| | |
| | def _create_top_categories_chart(self) -> Dict: |
| | """μ
μ’
λ³ μ ν¬ μ μ°¨νΈ""" |
| | if 'μκΆμ
μ’
μ€λΆλ₯λͺ
' not in self.df.columns: |
| | return None |
| | |
| | top_categories = self.df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].value_counts().head(15) |
| | fig = px.bar( |
| | x=top_categories.values, |
| | y=top_categories.index, |
| | orientation='h', |
| | labels={'x': 'μ ν¬ μ', 'y': 'μ
μ’
'}, |
| | title='π μμ μ
μ’
TOP 15', |
| | color=top_categories.values, |
| | color_continuous_scale='blues' |
| | ) |
| | fig.update_layout(showlegend=False, height=500) |
| | return {'type': 'plot', 'data': fig, 'title': 'μ
μ’
λ³ μ ν¬ μ λΆμ'} |
| | |
| | def _create_major_category_pie(self) -> Dict: |
| | """λλΆλ₯λ³ λΆν¬""" |
| | if 'μκΆμ
μ’
λλΆλ₯μ½λ' not in self.df.columns: |
| | return None |
| | |
| | major_counts = self.df['μκΆμ
μ’
λλΆλ₯μ½λ'].value_counts() |
| | labels = [MarketDataLoader.CATEGORY_MAPPING.get(code, code) for code in major_counts.index] |
| | |
| | fig = px.pie( |
| | values=major_counts.values, |
| | names=labels, |
| | title='π μ
μ’
λλΆλ₯ λΆν¬', |
| | hole=0.4, |
| | color_discrete_sequence=px.colors.qualitative.Set3 |
| | ) |
| | fig.update_traces(textposition='inside', textinfo='percent+label') |
| | return {'type': 'plot', 'data': fig, 'title': 'λλΆλ₯λ³ μκΆ κ΅¬μ±'} |
| | |
| | def _create_floor_analysis(self) -> Dict: |
| | """μΈ΅λ³ λΆν¬ μμΈ λΆμ""" |
| | if 'μΈ΅μ 보_μ«μ' not in self.df.columns: |
| | return None |
| | |
| | floor_data = self.df['μΈ΅μ 보_μ«μ'].dropna() |
| | floor_counts = floor_data.value_counts().sort_index() |
| | |
| | underground = floor_counts[floor_counts.index < 0].sum() |
| | first_floor = floor_counts.get(1, 0) |
| | upper_floors = floor_counts[floor_counts.index > 1].sum() |
| | |
| | fig = go.Figure(data=[ |
| | go.Bar( |
| | x=['μ§ν', '1μΈ΅', '2μΈ΅ μ΄μ'], |
| | y=[underground, first_floor, upper_floors], |
| | text=[f'{underground:,}<br>({underground/len(floor_data)*100:.1f}%)', |
| | f'{first_floor:,}<br>({first_floor/len(floor_data)*100:.1f}%)', |
| | f'{upper_floors:,}<br>({upper_floors/len(floor_data)*100:.1f}%)'], |
| | textposition='auto', |
| | marker_color=['#e74c3c', '#3498db', '#95a5a6'] |
| | ) |
| | ]) |
| | fig.update_layout( |
| | title='π’ μΈ΅λ³ μ ν¬ λΆν¬ (μ§ν vs 1μΈ΅ vs μμΈ΅)', |
| | xaxis_title='μΈ΅ ꡬλΆ', |
| | yaxis_title='μ ν¬ μ', |
| | height=400 |
| | ) |
| | return {'type': 'plot', 'data': fig, 'title': 'μΈ΅λ³ μ
μ§ λΆμ'} |
| | |
| | def _create_diversity_index(self) -> Dict: |
| | """μ§μλ³ μ
μ’
λ€μμ± μ§μ""" |
| | if 'μꡰꡬλͺ
' not in self.df.columns or 'μκΆμ
μ’
μ€λΆλ₯λͺ
' not in self.df.columns: |
| | return None |
| | |
| | diversity_data = [] |
| | for district in self.df['μꡰꡬλͺ
'].unique()[:20]: |
| | district_df = self.df[self.df['μꡰꡬλͺ
'] == district] |
| | num_categories = district_df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].nunique() |
| | total_stores = len(district_df) |
| | diversity_score = (num_categories / total_stores) * 100 |
| | diversity_data.append({ |
| | 'μ§μ': district, |
| | 'λ€μμ±μ§μ': diversity_score, |
| | 'μ
μ’
μ': num_categories, |
| | 'μ ν¬μ': total_stores |
| | }) |
| | |
| | diversity_df = pd.DataFrame(diversity_data).sort_values('λ€μμ±μ§μ', ascending=False) |
| | |
| | fig = px.bar( |
| | diversity_df, |
| | x='λ€μμ±μ§μ', |
| | y='μ§μ', |
| | orientation='h', |
| | title='π¨ μ§μλ³ μ
μ’
λ€μμ± μ§μ (μ
μ’
μ / μ ν¬ μ Γ 100)', |
| | labels={'λ€μμ±μ§μ': 'λ€μμ± μ§μ', 'μ§μ': 'μꡰꡬ'}, |
| | color='λ€μμ±μ§μ', |
| | color_continuous_scale='viridis' |
| | ) |
| | fig.update_layout(height=500) |
| | return {'type': 'plot', 'data': fig, 'title': 'μκΆ λ€μμ± λΆμ'} |
| | |
| | def _create_franchise_analysis(self) -> Dict: |
| | """νλμ°¨μ΄μ¦ vs κ°μΈμ¬μ
μ λΆμ""" |
| | if 'λΈλλλͺ
' not in self.df.columns: |
| | return None |
| | |
| | franchise_count = self.df['λΈλλλͺ
'].notna().sum() |
| | individual_count = self.df['λΈλλλͺ
'].isna().sum() |
| | |
| | fig = go.Figure(data=[ |
| | go.Pie( |
| | labels=['κ°μΈμ¬μ
μ', 'νλμ°¨μ΄μ¦'], |
| | values=[individual_count, franchise_count], |
| | hole=0.4, |
| | marker_colors=['#3498db', '#e74c3c'], |
| | textinfo='label+percent+value', |
| | texttemplate='%{label}<br>%{value:,}κ°<br>(%{percent})' |
| | ) |
| | ]) |
| | |
| | fig.update_layout( |
| | title='πͺ κ°μΈμ¬μ
μ vs νλμ°¨μ΄μ¦ λΉμ¨', |
| | height=400 |
| | ) |
| | return {'type': 'plot', 'data': fig, 'title': 'μ¬μ
μ μ ν λΆμ'} |
| | |
| | def _create_floor_preference(self) -> Dict: |
| | """μ
μ’
λ³ μΈ΅ μ νΈλ""" |
| | if 'μΈ΅μ 보_μ«μ' not in self.df.columns or 'μκΆμ
μ’
μ€λΆλ₯λͺ
' not in self.df.columns: |
| | return None |
| | |
| | top_categories = self.df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].value_counts().head(10).index |
| | floor_pref_data = [] |
| | |
| | for category in top_categories: |
| | cat_df = self.df[self.df['μκΆμ
μ’
μ€λΆλ₯λͺ
'] == category] |
| | floor_dist = cat_df['μΈ΅μ 보_μ«μ'].dropna() |
| | |
| | if len(floor_dist) > 0: |
| | underground = (floor_dist < 0).sum() |
| | first_floor = (floor_dist == 1).sum() |
| | upper_floors = (floor_dist > 1).sum() |
| | |
| | floor_pref_data.append({ |
| | 'μ
μ’
': category, |
| | 'μ§ν': underground, |
| | '1μΈ΅': first_floor, |
| | '2μΈ΅ μ΄μ': upper_floors |
| | }) |
| | |
| | pref_df = pd.DataFrame(floor_pref_data) |
| | |
| | fig = go.Figure() |
| | fig.add_trace(go.Bar(name='μ§ν', x=pref_df['μ
μ’
'], y=pref_df['μ§ν'], marker_color='#e74c3c')) |
| | fig.add_trace(go.Bar(name='1μΈ΅', x=pref_df['μ
μ’
'], y=pref_df['1μΈ΅'], marker_color='#3498db')) |
| | fig.add_trace(go.Bar(name='2μΈ΅ μ΄μ', x=pref_df['μ
μ’
'], y=pref_df['2μΈ΅ μ΄μ'], marker_color='#95a5a6')) |
| | |
| | fig.update_layout( |
| | title='π’ μ
μ’
λ³ μΈ΅ μ νΈλ (μμ 10κ° μ
μ’
)', |
| | xaxis_title='μ
μ’
', |
| | yaxis_title='μ ν¬ μ', |
| | barmode='stack', |
| | height=500, |
| | xaxis_tickangle=-45 |
| | ) |
| | return {'type': 'plot', 'data': fig, 'title': 'μΈ΅λ³ μ νΈλ λΆμ'} |
| | |
| | def _create_district_density(self) -> Dict: |
| | """μκ΅°κ΅¬λ³ μκΆ λ°μ§λ""" |
| | if 'μꡰꡬλͺ
' not in self.df.columns: |
| | return None |
| | |
| | district_counts = self.df['μꡰꡬλͺ
'].value_counts().head(20) |
| | |
| | fig = px.bar( |
| | x=district_counts.values, |
| | y=district_counts.index, |
| | orientation='h', |
| | title='π μκ΅°κ΅¬λ³ μ ν¬ λ°μ§λ TOP 20', |
| | labels={'x': 'μ ν¬ μ', 'y': 'μꡰꡬ'}, |
| | color=district_counts.values, |
| | color_continuous_scale='reds' |
| | ) |
| | fig.update_layout(showlegend=False, height=600) |
| | return {'type': 'plot', 'data': fig, 'title': 'μ§μ λ°μ§λ λΆμ'} |
| | |
| | def _create_category_correlation(self) -> Dict: |
| | """μ
μ’
μκ΄κ΄κ³""" |
| | if 'μꡰꡬλͺ
' not in self.df.columns or 'μκΆμ
μ’
μ€λΆλ₯λͺ
' not in self.df.columns: |
| | return None |
| | |
| | top_categories = self.df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].value_counts().head(10).index.tolist() |
| | districts = self.df['μꡰꡬλͺ
'].unique() |
| | correlation_matrix = np.zeros((len(top_categories), len(top_categories))) |
| | |
| | for i, cat1 in enumerate(top_categories): |
| | for j, cat2 in enumerate(top_categories): |
| | if i != j: |
| | coexist_count = 0 |
| | for district in districts: |
| | district_df = self.df[self.df['μꡰꡬλͺ
'] == district] |
| | has_cat1 = cat1 in district_df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].values |
| | has_cat2 = cat2 in district_df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].values |
| | if has_cat1 and has_cat2: |
| | coexist_count += 1 |
| | correlation_matrix[i][j] = coexist_count |
| | |
| | fig = go.Figure(data=go.Heatmap( |
| | z=correlation_matrix, |
| | x=top_categories, |
| | y=top_categories, |
| | colorscale='Blues', |
| | text=np.round(correlation_matrix, 1), |
| | texttemplate='%{text}', |
| | textfont={"size": 10} |
| | )) |
| | |
| | fig.update_layout( |
| | title='π μ
μ’
μκ΄κ΄κ³ λ§€νΈλ¦μ€ (κ°μ μ§μ λμ μΆνμ¨)', |
| | xaxis_title='μ
μ’
', |
| | yaxis_title='μ
μ’
', |
| | height=600, |
| | xaxis_tickangle=-45 |
| | ) |
| | return {'type': 'plot', 'data': fig, 'title': 'μ
μ’
곡쑴 λΆμ'} |
| | |
| | def _create_subcategory_trends(self) -> Dict: |
| | """μλΆλ₯ νΈλ λ""" |
| | if 'μκΆμ
μ’
μλΆλ₯λͺ
' not in self.df.columns: |
| | return None |
| | |
| | subcat_counts = self.df['μκΆμ
μ’
μλΆλ₯λͺ
'].value_counts().head(20) |
| | |
| | fig = px.treemap( |
| | names=subcat_counts.index, |
| | parents=[''] * len(subcat_counts), |
| | values=subcat_counts.values, |
| | title='π μλΆλ₯ μ
μ’
νΈλ λ TOP 20', |
| | color=subcat_counts.values, |
| | color_continuous_scale='greens' |
| | ) |
| | fig.update_layout(height=600) |
| | return {'type': 'plot', 'data': fig, 'title': 'μΈλΆ μ
μ’
λΆμ'} |
| | |
| | def _create_regional_specialization(self) -> Dict: |
| | """μ§μλ³ νΉν μ
μ’
""" |
| | if 'μλλͺ
' not in self.df.columns or 'μκΆμ
μ’
μ€λΆλ₯λͺ
' not in self.df.columns: |
| | return None |
| | |
| | specialization_data = [] |
| | for region in self.df['μλλͺ
'].unique(): |
| | region_df = self.df[self.df['μλλͺ
'] == region] |
| | top_categories = region_df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].value_counts().head(3) |
| | for category, count in top_categories.items(): |
| | specialization_data.append({ |
| | 'μ§μ': region, |
| | 'νΉνμ
μ’
': category, |
| | 'μ ν¬μ': count |
| | }) |
| | |
| | spec_df = pd.DataFrame(specialization_data) |
| | |
| | fig = px.sunburst( |
| | spec_df, |
| | path=['μ§μ', 'νΉνμ
μ’
'], |
| | values='μ ν¬μ', |
| | title='π― μ§μλ³ νΉν μ
μ’
(κ° μ§μ TOP 3)', |
| | color='μ ν¬μ', |
| | color_continuous_scale='oranges' |
| | ) |
| | fig.update_layout(height=700) |
| | return {'type': 'plot', 'data': fig, 'title': 'μ§μ νΉν λΆμ'} |
| | |
| | def create_density_map(self, sample_size: int = 1000) -> str: |
| | """μ ν¬ λ°μ§λ μ§λ μμ±""" |
| | df_sample = self.df.sample(n=min(sample_size, len(self.df)), random_state=42) |
| | |
| | center_lat = df_sample['μλ'].mean() |
| | center_lon = df_sample['κ²½λ'].mean() |
| | |
| | m = folium.Map(location=[center_lat, center_lon], zoom_start=11, tiles='OpenStreetMap') |
| | |
| | heat_data = [[row['μλ'], row['κ²½λ']] for _, row in df_sample.iterrows()] |
| | HeatMap(heat_data, radius=15, blur=25, max_zoom=13).add_to(m) |
| | |
| | return m._repr_html_() |
| | |
| | def analyze_for_llm(self) -> Dict: |
| | """LLM 컨ν
μ€νΈμ© λΆμ λ°μ΄ν°""" |
| | context = { |
| | 'μ΄_μ ν¬_μ': len(self.df), |
| | 'μ§μ_μ': self.df['μλλͺ
'].nunique() if 'μλλͺ
' in self.df.columns else 0, |
| | 'μ
μ’
_μ': self.df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].nunique() if 'μκΆμ
μ’
μ€λΆλ₯λͺ
' in self.df.columns else 0, |
| | } |
| | |
| | if 'μκΆμ
μ’
μ€λΆλ₯λͺ
' in self.df.columns: |
| | context['μμ_μ
μ’
_5'] = self.df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].value_counts().head(5).to_dict() |
| | |
| | if 'μΈ΅μ 보_μ«μ' in self.df.columns: |
| | first_floor_ratio = (self.df['μΈ΅μ 보_μ«μ'] == 1).sum() / len(self.df) * 100 |
| | context['1μΈ΅_λΉμ¨'] = f"{first_floor_ratio:.1f}%" |
| | |
| | return context |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | class LLMQueryProcessor: |
| | """Fireworks AI κΈ°λ° μμ°μ΄ μ²λ¦¬ (μ€νΈλ¦¬λ° μ§μ + μΉκ²μ)""" |
| | |
| | def __init__(self, api_key: str = None): |
| | self.api_key = api_key or os.getenv("FIREWORKS_API_KEY") |
| | self.base_url = "https://api.fireworks.ai/inference/v1/chat/completions" |
| | |
| | if not self.api_key: |
| | raise ValueError("β FIREWORKS_API_KEY νκ²½λ³μλ₯Ό μ€μ νκ±°λ API ν€λ₯Ό μ
λ ₯ν΄μ£ΌμΈμ!") |
| | |
| | def process_query_stream(self, query: str, data_context: Dict, chat_history: List = None, web_search_results: str = None): |
| | """μμ°μ΄ 쿼리 μ²λ¦¬ (μ€νΈλ¦¬λ° λͺ¨λ) - μΉκ²μ κ²°κ³Ό ν¬ν¨""" |
| | |
| | web_context = "" |
| | if web_search_results and "β οΈ" not in web_search_results: |
| | web_context = f""" |
| | |
| | π **μ΅μ μΉ κ²μ μ 보** |
| | {web_search_results} |
| | |
| | μ μΉ κ²μ κ²°κ³Όλ₯Ό μ°Έκ³ νμ¬ μ΅μ μ 보μ νΈλ λλ₯Ό λ°μν΄μ£ΌμΈμ. |
| | """ |
| |
|
| | system_prompt = f"""λΉμ μ νκ΅ μκΆ λ°μ΄ν° λΆμ μ λ¬Έκ°μ
λλ€. |
| | |
| | π **νμ¬ λΆμ λ°μ΄ν°** |
| | {json.dumps(data_context, ensure_ascii=False, indent=2)} |
| | {web_context} |
| | |
| | ꡬ체μ μΈ μ«μμ λΉμ¨λ‘ μ λμ λΆμμ μ 곡νμΈμ. |
| | μ°½μ
, ν¬μ, κ²½μ λΆμ κ΄μ μμ μ€μ©μ μΈμ¬μ΄νΈλ₯Ό μ 곡νμΈμ. |
| | μΉ κ²μ κ²°κ³Όκ° μ 곡λ κ²½μ° μ΅μ νΈλ λμ ν¨κ» λΆμνμΈμ. |
| | λ°λμ νκ΅μ΄λ‘ λ΅λ³νμΈμ.""" |
| |
|
| | messages = [{"role": "system", "content": system_prompt}] |
| | if chat_history: |
| | messages.extend(chat_history[-6:]) |
| | messages.append({"role": "user", "content": query}) |
| | |
| | payload = { |
| | "model": "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507", |
| | "max_tokens": 4800, |
| | "temperature": 0.7, |
| | "messages": messages, |
| | "stream": True |
| | } |
| | |
| | headers = { |
| | "Authorization": f"Bearer {self.api_key}", |
| | "Content-Type": "application/json" |
| | } |
| | |
| | try: |
| | response = requests.post( |
| | self.base_url, |
| | headers=headers, |
| | json=payload, |
| | timeout=60, |
| | stream=True |
| | ) |
| | |
| | if response.status_code == 200: |
| | for line in response.iter_lines(): |
| | if line: |
| | line_text = line.decode('utf-8') |
| | if line_text.startswith('data: '): |
| | data_str = line_text[6:] |
| | if data_str.strip() == '[DONE]': |
| | break |
| | try: |
| | data = json.loads(data_str) |
| | if 'choices' in data and len(data['choices']) > 0: |
| | delta = data['choices'][0].get('delta', {}) |
| | content = delta.get('content', '') |
| | if content: |
| | yield content |
| | except json.JSONDecodeError: |
| | continue |
| | else: |
| | yield f"β οΈ API μ€λ₯: {response.status_code}" |
| | |
| | except requests.exceptions.Timeout: |
| | yield "β οΈ API μλ΅ μκ° μ΄κ³Ό. μ μ ν λ€μ μλν΄μ£ΌμΈμ." |
| | except requests.exceptions.ConnectionError: |
| | yield "β οΈ λ€νΈμν¬ μ°κ²° μ€λ₯. μΈν°λ· μ°κ²°μ νμΈν΄μ£ΌμΈμ." |
| | except Exception as e: |
| | yield f"β μ€λ₯: {str(e)}" |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | class AppState: |
| | def __init__(self): |
| | self.analyzer = None |
| | self.llm_processor = None |
| | self.brave_client = None |
| | self.chat_history = [] |
| |
|
| | app_state = AppState() |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def load_data(regions): |
| | """λ°μ΄ν° λ‘λ""" |
| | if not regions: |
| | return "β μ΅μ 1κ° μ§μμ μ νν΄μ£ΌμΈμ!", None, None, None |
| | |
| | try: |
| | df = MarketDataLoader.load_multiple_regions(regions, sample_per_region=30000) |
| | if df.empty: |
| | return "β λ°μ΄ν° λ‘λ μ€ν¨!", None, None, None |
| | |
| | app_state.analyzer = MarketAnalyzer(df) |
| | |
| | stats = f""" |
| | β
**λ°μ΄ν° λ‘λ μλ£!** |
| | {'=' * 40} |
| | π **λΆμ ν΅κ³** |
| | β’ μ΄ μ ν¬: {len(df):,}κ° |
| | β’ λΆμ μ§μ: {', '.join(regions)} |
| | β’ μ
μ’
μ: {df['μκΆμ
μ’
μ€λΆλ₯λͺ
'].nunique()}κ° |
| | β’ λλΆλ₯: {df['μκΆμ
μ’
λλΆλ₯λͺ
'].nunique()}κ° |
| | {'=' * 40} |
| | π‘ μ΄μ μΈμ¬μ΄νΈλ₯Ό νμΈνκ±°λ AIμκ² μ§λ¬ΈνμΈμ! |
| | """ |
| | |
| | return stats, gr.update(visible=True), gr.update(visible=True), gr.update(visible=True) |
| | except Exception as e: |
| | return f"β μ€λ₯: {str(e)}", None, None, None |
| |
|
| |
|
| | def generate_insights(): |
| | """μΈμ¬μ΄νΈ μμ±""" |
| | if app_state.analyzer is None: |
| | return [None] * 11 |
| | |
| | insights = app_state.analyzer.get_comprehensive_insights() |
| | map_html = app_state.analyzer.create_density_map(sample_size=2000) |
| | |
| | result = [map_html] |
| | for insight in insights: |
| | if insight and insight['type'] == 'plot': |
| | result.append(insight['data']) |
| | else: |
| | result.append(None) |
| | |
| | while len(result) < 11: |
| | result.append(None) |
| | |
| | return result[:11] |
| |
|
| |
|
| | def chat_respond(message, history): |
| | """μ±λ΄ μλ΅ (μ€νΈλ¦¬λ° λͺ¨λ + μΉκ²μ)""" |
| | if app_state.analyzer is None: |
| | yield history + [[message, "β λ¨Όμ λ°μ΄ν°λ₯Ό λ‘λν΄μ£ΌμΈμ!"]] |
| | return |
| | |
| | data_context = app_state.analyzer.analyze_for_llm() |
| | |
| | try: |
| | if app_state.llm_processor is None: |
| | app_state.llm_processor = LLMQueryProcessor() |
| | |
| | if app_state.brave_client is None: |
| | try: |
| | app_state.brave_client = BraveSearchClient() |
| | except: |
| | app_state.brave_client = None |
| | |
| | web_results = None |
| | if app_state.brave_client and app_state.brave_client.api_key: |
| | search_query = f"νκ΅ μκΆ μ°½μ
νΈλ λ {message}" |
| | web_results = app_state.brave_client.search(search_query, count=3) |
| | |
| | chat_hist = [] |
| | for user_msg, bot_msg in history: |
| | chat_hist.append({"role": "user", "content": user_msg}) |
| | chat_hist.append({"role": "assistant", "content": bot_msg}) |
| | |
| | history = history + [[message, ""]] |
| | |
| | if web_results and "β οΈ" not in web_results: |
| | history[-1][1] = "π μΉ κ²μ μ€...\n\n" |
| | yield history |
| | |
| | full_response = "" |
| | for chunk in app_state.llm_processor.process_query_stream(message, data_context, chat_hist, web_results): |
| | full_response += chunk |
| | history[-1][1] = full_response |
| | yield history |
| | |
| | except ValueError as e: |
| | response = f"""π **κΈ°λ³Έ λ°μ΄ν° λΆμ κ²°κ³Ό** |
| | |
| | **μ 체 νν©** |
| | - μ΄ μ ν¬ μ: {data_context['μ΄_μ ν¬_μ']:,}κ° |
| | - μ
μ’
μ’
λ₯: {data_context['μ
μ’
_μ']}κ° |
| | - 1μΈ΅ λΉμ¨: {data_context.get('1μΈ΅_λΉμ¨', 'N/A')} |
| | |
| | β οΈ **AI λΆμ μ¬μ© λ°©λ²** |
| | νκ²½λ³μλ₯Ό μ€μ νμΈμ: |
| | ```bash |
| | export FIREWORKS_API_KEY="your_api_key_here" |
| | export BRAVE_API_KEY="your_brave_api_key_here" |
| | ```""" |
| | |
| | history = history + [[message, response]] |
| | yield history |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | css = """ |
| | /* ===== π¨ Google Fonts Import ===== */ |
| | @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&family=Noto+Sans+KR:wght@400;700&display=swap'); |
| | |
| | /* ===== π¨ Comic Classic λ°°κ²½ - λΉν°μ§ νμ΄νΌ + λνΈ ν¨ν΄ ===== */ |
| | .gradio-container { |
| | background-color: #FEF9C3 !important; |
| | background-image: |
| | radial-gradient(#1F2937 1px, transparent 1px) !important; |
| | background-size: 20px 20px !important; |
| | min-height: 100vh !important; |
| | font-family: 'Noto Sans KR', 'Comic Neue', cursive, sans-serif !important; |
| | } |
| | |
| | /* ===== νκΉ
νμ΄μ€ μλ¨ μμ μ¨κΉ ===== */ |
| | .huggingface-space-header, |
| | #space-header, |
| | .space-header, |
| | [class*="space-header"], |
| | .svelte-1ed2p3z, |
| | .space-header-badge, |
| | .header-badge, |
| | [data-testid="space-header"], |
| | .svelte-kqij2n, |
| | .svelte-1ax1toq, |
| | .embed-container > div:first-child { |
| | display: none !important; |
| | visibility: hidden !important; |
| | height: 0 !important; |
| | width: 0 !important; |
| | overflow: hidden !important; |
| | opacity: 0 !important; |
| | pointer-events: none !important; |
| | } |
| | |
| | /* ===== Footer μμ μ¨κΉ ===== */ |
| | footer, |
| | .footer, |
| | .gradio-container footer, |
| | .built-with, |
| | [class*="footer"], |
| | .gradio-footer, |
| | .main-footer, |
| | div[class*="footer"], |
| | .show-api, |
| | .built-with-gradio, |
| | a[href*="gradio.app"], |
| | a[href*="huggingface.co/spaces"] { |
| | display: none !important; |
| | visibility: hidden !important; |
| | height: 0 !important; |
| | padding: 0 !important; |
| | margin: 0 !important; |
| | } |
| | |
| | /* ===== λ©μΈ 컨ν
μ΄λ ===== */ |
| | #col-container { |
| | max-width: 1400px; |
| | margin: 0 auto; |
| | } |
| | |
| | /* ===== π¨ ν€λ νμ΄ν - μ½λ―Ή μ€νμΌ ===== */ |
| | .header-text h1 { |
| | font-family: 'Bangers', cursive !important; |
| | color: #1F2937 !important; |
| | font-size: 3.2rem !important; |
| | font-weight: 400 !important; |
| | text-align: center !important; |
| | margin-bottom: 0.5rem !important; |
| | text-shadow: |
| | 4px 4px 0px #FACC15, |
| | 6px 6px 0px #1F2937 !important; |
| | letter-spacing: 3px !important; |
| | -webkit-text-stroke: 2px #1F2937 !important; |
| | } |
| | |
| | /* ===== π¨ μλΈνμ΄ν ===== */ |
| | .subtitle { |
| | text-align: center !important; |
| | font-family: 'Noto Sans KR', 'Comic Neue', cursive !important; |
| | font-size: 1.1rem !important; |
| | color: #1F2937 !important; |
| | margin-bottom: 1.5rem !important; |
| | font-weight: 700 !important; |
| | } |
| | |
| | /* ===== π¨ μΉ΄λ/ν¨λ - λ§ν νλ μ μ€νμΌ ===== */ |
| | .gr-panel, |
| | .gr-box, |
| | .gr-form, |
| | .block, |
| | .gr-group { |
| | background: #FFFFFF !important; |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | box-shadow: 6px 6px 0px #1F2937 !important; |
| | transition: all 0.2s ease !important; |
| | } |
| | |
| | .gr-panel:hover, |
| | .block:hover { |
| | transform: translate(-2px, -2px) !important; |
| | box-shadow: 8px 8px 0px #1F2937 !important; |
| | } |
| | |
| | /* ===== π¨ μ
λ ₯ νλ (Textbox) ===== */ |
| | textarea, |
| | input[type="text"], |
| | input[type="number"] { |
| | background: #FFFFFF !important; |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | color: #1F2937 !important; |
| | font-family: 'Noto Sans KR', 'Comic Neue', cursive !important; |
| | font-size: 1rem !important; |
| | font-weight: 700 !important; |
| | transition: all 0.2s ease !important; |
| | } |
| | |
| | textarea:focus, |
| | input[type="text"]:focus, |
| | input[type="number"]:focus { |
| | border-color: #3B82F6 !important; |
| | box-shadow: 4px 4px 0px #3B82F6 !important; |
| | outline: none !important; |
| | } |
| | |
| | textarea::placeholder { |
| | color: #9CA3AF !important; |
| | font-weight: 400 !important; |
| | } |
| | |
| | /* ===== π¨ Primary λ²νΌ - μ½λ―Ή λΈλ£¨ ===== */ |
| | .gr-button-primary, |
| | button.primary, |
| | .gr-button.primary { |
| | background: #3B82F6 !important; |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | color: #FFFFFF !important; |
| | font-family: 'Noto Sans KR', 'Bangers', cursive !important; |
| | font-weight: 700 !important; |
| | font-size: 1.2rem !important; |
| | letter-spacing: 1px !important; |
| | padding: 14px 28px !important; |
| | box-shadow: 5px 5px 0px #1F2937 !important; |
| | transition: all 0.1s ease !important; |
| | text-shadow: 1px 1px 0px #1F2937 !important; |
| | } |
| | |
| | .gr-button-primary:hover, |
| | button.primary:hover, |
| | .gr-button.primary:hover { |
| | background: #2563EB !important; |
| | transform: translate(-2px, -2px) !important; |
| | box-shadow: 7px 7px 0px #1F2937 !important; |
| | } |
| | |
| | .gr-button-primary:active, |
| | button.primary:active, |
| | .gr-button.primary:active { |
| | transform: translate(3px, 3px) !important; |
| | box-shadow: 2px 2px 0px #1F2937 !important; |
| | } |
| | |
| | /* ===== π¨ Secondary λ²νΌ - μ½λ―Ή λ λ ===== */ |
| | .gr-button-secondary, |
| | button.secondary { |
| | background: #EF4444 !important; |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | color: #FFFFFF !important; |
| | font-family: 'Noto Sans KR', 'Bangers', cursive !important; |
| | font-weight: 700 !important; |
| | font-size: 1rem !important; |
| | letter-spacing: 1px !important; |
| | box-shadow: 4px 4px 0px #1F2937 !important; |
| | transition: all 0.1s ease !important; |
| | text-shadow: 1px 1px 0px #1F2937 !important; |
| | } |
| | |
| | .gr-button-secondary:hover, |
| | button.secondary:hover { |
| | background: #DC2626 !important; |
| | transform: translate(-2px, -2px) !important; |
| | box-shadow: 6px 6px 0px #1F2937 !important; |
| | } |
| | |
| | /* ===== π¨ Small λ²νΌ ===== */ |
| | button.sm, |
| | .gr-button-sm { |
| | background: #10B981 !important; |
| | border: 2px solid #1F2937 !important; |
| | border-radius: 6px !important; |
| | color: #FFFFFF !important; |
| | font-family: 'Noto Sans KR', cursive !important; |
| | font-weight: 700 !important; |
| | font-size: 0.9rem !important; |
| | padding: 8px 16px !important; |
| | box-shadow: 3px 3px 0px #1F2937 !important; |
| | transition: all 0.1s ease !important; |
| | } |
| | |
| | button.sm:hover, |
| | .gr-button-sm:hover { |
| | background: #059669 !important; |
| | transform: translate(-1px, -1px) !important; |
| | box-shadow: 4px 4px 0px #1F2937 !important; |
| | } |
| | |
| | /* ===== π¨ λ‘κ·Έ μΆλ ₯ μμ ===== */ |
| | .info-log textarea { |
| | background: #1F2937 !important; |
| | color: #10B981 !important; |
| | font-family: 'Courier New', monospace !important; |
| | font-size: 0.9rem !important; |
| | font-weight: 400 !important; |
| | border: 3px solid #10B981 !important; |
| | border-radius: 8px !important; |
| | box-shadow: 4px 4px 0px #10B981 !important; |
| | } |
| | |
| | /* ===== π¨ μμ½λμΈ - λ§νμ μ€νμΌ ===== */ |
| | .gr-accordion { |
| | background: #FACC15 !important; |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | box-shadow: 4px 4px 0px #1F2937 !important; |
| | } |
| | |
| | .gr-accordion-header { |
| | color: #1F2937 !important; |
| | font-family: 'Noto Sans KR', 'Comic Neue', cursive !important; |
| | font-weight: 700 !important; |
| | font-size: 1.1rem !important; |
| | } |
| | |
| | /* ===== π¨ 체ν¬λ°μ€ κ·Έλ£Ή ===== */ |
| | .gr-checkbox-group { |
| | background: #FFFFFF !important; |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | padding: 10px !important; |
| | } |
| | |
| | input[type="checkbox"] { |
| | accent-color: #3B82F6 !important; |
| | width: 18px !important; |
| | height: 18px !important; |
| | } |
| | |
| | /* ===== π¨ ν μ€νμΌ ===== */ |
| | .gr-tab-nav { |
| | background: #FACC15 !important; |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px 8px 0 0 !important; |
| | box-shadow: 4px 4px 0px #1F2937 !important; |
| | } |
| | |
| | .gr-tab-nav button { |
| | font-family: 'Noto Sans KR', 'Comic Neue', cursive !important; |
| | font-weight: 700 !important; |
| | color: #1F2937 !important; |
| | border: none !important; |
| | padding: 12px 20px !important; |
| | } |
| | |
| | .gr-tab-nav button.selected { |
| | background: #3B82F6 !important; |
| | color: #FFFFFF !important; |
| | border-radius: 6px 6px 0 0 !important; |
| | } |
| | |
| | /* ===== π¨ μ±λ΄ μ€νμΌ ===== */ |
| | .gr-chatbot { |
| | background: #FFFFFF !important; |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | box-shadow: 6px 6px 0px #1F2937 !important; |
| | } |
| | |
| | .gr-chatbot .message { |
| | font-family: 'Noto Sans KR', sans-serif !important; |
| | } |
| | |
| | /* ===== π¨ λΌλ²¨ μ€νμΌ ===== */ |
| | label, |
| | .gr-input-label, |
| | .gr-block-label { |
| | color: #1F2937 !important; |
| | font-family: 'Noto Sans KR', 'Comic Neue', cursive !important; |
| | font-weight: 700 !important; |
| | font-size: 1rem !important; |
| | } |
| | |
| | /* ===== π¨ Markdown μ€νμΌ ===== */ |
| | .gr-markdown { |
| | font-family: 'Noto Sans KR', 'Comic Neue', cursive !important; |
| | color: #1F2937 !important; |
| | } |
| | |
| | .gr-markdown h1, |
| | .gr-markdown h2, |
| | .gr-markdown h3 { |
| | font-family: 'Bangers', 'Noto Sans KR', cursive !important; |
| | color: #1F2937 !important; |
| | text-shadow: 2px 2px 0px #FACC15 !important; |
| | } |
| | |
| | /* ===== π¨ Plot μμ ===== */ |
| | .gr-plot { |
| | border: 3px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | box-shadow: 4px 4px 0px #1F2937 !important; |
| | background: #FFFFFF !important; |
| | } |
| | |
| | /* ===== π¨ HTML μμ (μ§λ) ===== */ |
| | .gr-html { |
| | border: 4px solid #1F2937 !important; |
| | border-radius: 8px !important; |
| | box-shadow: 6px 6px 0px #FACC15 !important; |
| | overflow: hidden !important; |
| | } |
| | |
| | /* ===== π¨ μ€ν¬λ‘€λ° - μ½λ―Ή μ€νμΌ ===== */ |
| | ::-webkit-scrollbar { |
| | width: 12px; |
| | height: 12px; |
| | } |
| | |
| | ::-webkit-scrollbar-track { |
| | background: #FEF9C3; |
| | border: 2px solid #1F2937; |
| | } |
| | |
| | ::-webkit-scrollbar-thumb { |
| | background: #3B82F6; |
| | border: 2px solid #1F2937; |
| | border-radius: 0px; |
| | } |
| | |
| | ::-webkit-scrollbar-thumb:hover { |
| | background: #EF4444; |
| | } |
| | |
| | /* ===== π¨ μ ν νμ΄λΌμ΄νΈ ===== */ |
| | ::selection { |
| | background: #FACC15; |
| | color: #1F2937; |
| | } |
| | |
| | /* ===== π¨ λ§ν¬ μ€νμΌ ===== */ |
| | a { |
| | color: #3B82F6 !important; |
| | text-decoration: none !important; |
| | font-weight: 700 !important; |
| | } |
| | |
| | a:hover { |
| | color: #EF4444 !important; |
| | } |
| | |
| | /* ===== π¨ Row/Column κ°κ²© ===== */ |
| | .gr-row { |
| | gap: 1.5rem !important; |
| | } |
| | |
| | .gr-column { |
| | gap: 1rem !important; |
| | } |
| | |
| | /* ===== π¨ Badge μ€νμΌ ===== */ |
| | .badge-container { |
| | display: flex; |
| | justify-content: center; |
| | gap: 15px; |
| | flex-wrap: wrap; |
| | margin: 20px 0; |
| | } |
| | |
| | .comic-badge { |
| | display: inline-flex; |
| | align-items: center; |
| | gap: 8px; |
| | padding: 12px 24px; |
| | border: 3px solid #1F2937; |
| | border-radius: 8px; |
| | text-decoration: none; |
| | font-weight: 700; |
| | font-size: 1em; |
| | transition: all 0.2s ease; |
| | box-shadow: 4px 4px 0px #1F2937; |
| | font-family: 'Noto Sans KR', sans-serif; |
| | } |
| | |
| | .comic-badge:hover { |
| | transform: translate(-2px, -2px); |
| | box-shadow: 6px 6px 0px #1F2937; |
| | } |
| | |
| | .comic-badge-yellow { |
| | background: #FACC15; |
| | color: #1F2937; |
| | } |
| | |
| | .comic-badge-blue { |
| | background: #3B82F6; |
| | color: #FFFFFF; |
| | } |
| | |
| | .comic-badge-green { |
| | background: #10B981; |
| | color: #FFFFFF; |
| | } |
| | |
| | /* ===== λ°μν μ‘°μ ===== */ |
| | @media (max-width: 768px) { |
| | .header-text h1 { |
| | font-size: 2rem !important; |
| | text-shadow: |
| | 3px 3px 0px #FACC15, |
| | 4px 4px 0px #1F2937 !important; |
| | } |
| | |
| | .gr-button-primary, |
| | button.primary { |
| | padding: 12px 20px !important; |
| | font-size: 1rem !important; |
| | } |
| | |
| | .gr-panel, |
| | .block { |
| | box-shadow: 4px 4px 0px #1F2937 !important; |
| | } |
| | } |
| | |
| | /* ===== π¨ λ€ν¬λͺ¨λ λΉνμ±ν ===== */ |
| | @media (prefers-color-scheme: dark) { |
| | .gradio-container { |
| | background-color: #FEF9C3 !important; |
| | } |
| | } |
| | """ |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | with gr.Blocks(title="AI μκΆ λΆμ μμ€ν
", css=css) as demo: |
| | |
| | |
| | gr.HTML(""" |
| | <div style="text-align: center; margin: 20px 0 10px 0;"> |
| | <a href="https://www.humangen.ai" target="_blank" style="text-decoration: none;"> |
| | <img src="https://img.shields.io/static/v1?label=π HOME&message=HUMANGEN.AI&color=0000ff&labelColor=ffcc00&style=for-the-badge" alt="HOME"> |
| | </a> |
| | </div> |
| | """) |
| | |
| | |
| | gr.Markdown( |
| | """ |
| | # πͺ AI μκΆ λΆμ μμ€ν
PRO π |
| | """, |
| | elem_classes="header-text" |
| | ) |
| | |
| | gr.Markdown( |
| | """ |
| | <p class="subtitle">β‘ μ κ΅ μκ°(μκΆ) λ°μ΄ν° μ€μκ° λΆμ | μ€νΈλ¦¬λ° + μΉκ²μ π | 10κ°μ§ μ¬μΈ΅ μΈμ¬μ΄νΈ π</p> |
| | """, |
| | ) |
| | |
| | |
| | gr.HTML(""" |
| | <div class="badge-container"> |
| | <a href="https://open.kakao.com/o/peIe8KWh" target="_blank" class="comic-badge comic-badge-yellow"> |
| | <span>π¬</span> |
| | <span>μ€νμ±ν
λ°λ‘κ°κΈ°</span> |
| | </a> |
| | <a href="https://ginigen.ai" target="_blank" class="comic-badge comic-badge-blue"> |
| | <span>π</span> |
| | <span>λλ
Έ λ°λλ μ λμ¨ λ¬΄λ£ μλΉμ€</span> |
| | </a> |
| | </div> |
| | """) |
| | |
| | |
| | api_status = "β
μ€μ λ¨" if os.getenv("FIREWORKS_API_KEY") else "β οΈ λ―Έμ€μ " |
| | brave_status = "β
νμ±ν" if os.getenv("BRAVE_API_KEY") else "β οΈ λΉνμ±ν" |
| | |
| | with gr.Row(equal_height=False): |
| | |
| | with gr.Column(scale=1, min_width=300): |
| | gr.Markdown("### βοΈ λΆμ μ€μ ") |
| | |
| | gr.Markdown(f""" |
| | **π API μν** |
| | - Fireworks AI: {api_status} |
| | - Brave Search: {brave_status} |
| | """) |
| | |
| | region_select = gr.CheckboxGroup( |
| | choices=list(MarketDataLoader.REGIONS.keys()), |
| | value=['μμΈ'], |
| | label="π λΆμ μ§μ μ ν (μ΅λ 5κ° κΆμ₯)" |
| | ) |
| | |
| | load_btn = gr.Button( |
| | "π λ°μ΄ν° λ‘λνκΈ°!", |
| | variant="primary", |
| | size="lg" |
| | ) |
| | |
| | with gr.Accordion("π λ‘λ μν", open=True): |
| | status_box = gr.Markdown( |
| | "π μ§μμ μ ννκ³ λ°μ΄ν°λ₯Ό λ‘λνμΈμ!", |
| | elem_classes="info-log" |
| | ) |
| | |
| | |
| | with gr.Column(scale=3, min_width=600): |
| | with gr.Tabs() as tabs: |
| | |
| | with gr.Tab("π μΈμ¬μ΄νΈ λμ보λ", id=0) as tab1: |
| | insights_content = gr.Column(visible=False) |
| | |
| | with insights_content: |
| | gr.Markdown("### πΊοΈ μ ν¬ λ°μ§λ ννΈλ§΅") |
| | map_output = gr.HTML() |
| | |
| | gr.Markdown("---") |
| | gr.Markdown("### π 10κ°μ§ μ¬μΈ΅ μκΆ μΈμ¬μ΄νΈ") |
| | |
| | with gr.Row(): |
| | chart1 = gr.Plot(label="π μ
μ’
λ³ μ ν¬ μ") |
| | chart2 = gr.Plot(label="π λλΆλ₯ λΆν¬") |
| | |
| | with gr.Row(): |
| | chart3 = gr.Plot(label="π’ μΈ΅λ³ λΆν¬") |
| | chart4 = gr.Plot(label="π¨ μ
μ’
λ€μμ±") |
| | |
| | with gr.Row(): |
| | chart5 = gr.Plot(label="πͺ νλμ°¨μ΄μ¦ λΆμ") |
| | chart6 = gr.Plot(label="π μΈ΅ μ νΈλ") |
| | |
| | with gr.Row(): |
| | chart7 = gr.Plot(label="π₯ μ§μ λ°μ§λ") |
| | chart8 = gr.Plot(label="π μ
μ’
μκ΄κ΄κ³") |
| | |
| | with gr.Row(): |
| | chart9 = gr.Plot(label="π μλΆλ₯ νΈλ λ") |
| | chart10 = gr.Plot(label="π― μ§μ νΉν") |
| | |
| | |
| | with gr.Tab("π€ AI λΆμ μ±λ΄ β‘π", id=1) as tab2: |
| | chat_content = gr.Column(visible=False) |
| | |
| | with chat_content: |
| | gr.Markdown(""" |
| | ### π‘ μμ μ§λ¬Έ |
| | κ°λ¨μμ μΉ΄ν μ°½μ
? | μΉν¨μ§ ν¬ν μ§μ? | 1μΈ΅μ΄ μ 리ν μ
μ’
? | νλμ°¨μ΄μ¦ μ μ μ¨? |
| | |
| | β‘ **μ€νΈλ¦¬λ°**: AI μλ΅μ΄ μ€μκ°μΌλ‘ νμλ©λλ€! |
| | π **μΉκ²μ**: μ΅μ μκΆ νΈλ λλ₯Ό μλ λ°μν©λλ€! |
| | """) |
| | |
| | chatbot = gr.Chatbot( |
| | height=450, |
| | label="AI μκΆ λΆμ μ΄μμ€ν΄νΈ" |
| | ) |
| | |
| | with gr.Row(): |
| | msg_input = gr.Textbox( |
| | placeholder="무μμ΄λ λ¬Όμ΄λ³΄μΈμ! (μ: κ°λ¨μμ μΉ΄ν μ°½μ
νλ €λ©΄?)", |
| | show_label=False, |
| | scale=4 |
| | ) |
| | submit_btn = gr.Button("π μ μ‘", variant="primary", scale=1) |
| | |
| | with gr.Row(): |
| | sample_btn1 = gr.Button("β κ°λ¨ μΉ΄ν μ°½μ
?", size="sm") |
| | sample_btn2 = gr.Button("π μΉν¨μ§ ν¬ν μ§μ?", size="sm") |
| | sample_btn3 = gr.Button("π’ 1μΈ΅ μ 리ν μ
μ’
?", size="sm") |
| | sample_btn4 = gr.Button("πͺ νλμ°¨μ΄μ¦ μ μ μ¨?", size="sm") |
| | |
| | |
| | gr.Markdown(""" |
| | --- |
| | ### π μ¬μ© κ°μ΄λ |
| | 1οΈβ£ μ§μ μ ν β 2οΈβ£ λ°μ΄ν° λ‘λ β 3οΈβ£ 10κ°μ§ μΈμ¬μ΄νΈ νμΈ λλ AIμκ² μ§λ¬Έ! |
| | |
| | ### π μ 곡λλ 10κ°μ§ λΆμ |
| | | λΆμ νλͺ© | μ€λͺ
| |
| | |----------|------| |
| | | π μ
μ’
λ³ μ ν¬ μ | κ°μ₯ λ§μ μ
μ’
TOP 15 | |
| | | π λλΆλ₯ λΆν¬ | μλ§€/μμ/μλΉμ€ λ± λΉμ¨ | |
| | | π’ μΈ΅λ³ λΆν¬ | μ§ν/1μΈ΅/μμΈ΅ μ
μ§ λΆμ | |
| | | π¨ μ
μ’
λ€μμ± | μ§μλ³ μ
μ’
λ€μμ± μ§μ | |
| | | πͺ νλμ°¨μ΄μ¦ λΆμ | κ°μΈ vs νλμ°¨μ΄μ¦ λΉμ¨ | |
| | | π μΈ΅ μ νΈλ | μ
μ’
λ³ μ νΈ μΈ΅μ | |
| | | π₯ μ§μ λ°μ§λ | μ ν¬ μ μμ μ§μ | |
| | | π μ
μ’
μκ΄κ΄κ³ | κ°μ΄ λνλλ μ
μ’
ν¨ν΄ | |
| | | π μλΆλ₯ νΈλ λ | μΈλΆ μ
μ’
λΆν¬ | |
| | | π― μ§μ νΉν | κ° μ§μμ νΉν μ
μ’
| |
| | |
| | π‘ **Tip**: API ν€ μμ΄λ 10κ°μ§ μκ°ν λΆμκ³Ό κΈ°λ³Έ ν΅κ³λ₯Ό νμΈν μ μμ΅λλ€! |
| | """) |
| |
|
| | |
| | load_btn.click( |
| | fn=load_data, |
| | inputs=[region_select], |
| | outputs=[status_box, insights_content, chat_content, tab1] |
| | ).then( |
| | fn=generate_insights, |
| | outputs=[map_output, chart1, chart2, chart3, chart4, chart5, chart6, chart7, chart8, chart9, chart10] |
| | ) |
| | |
| | |
| | submit_btn.click( |
| | fn=chat_respond, |
| | inputs=[msg_input, chatbot], |
| | outputs=[chatbot] |
| | ).then( |
| | fn=lambda: "", |
| | outputs=[msg_input] |
| | ) |
| | |
| | msg_input.submit( |
| | fn=chat_respond, |
| | inputs=[msg_input, chatbot], |
| | outputs=[chatbot] |
| | ).then( |
| | fn=lambda: "", |
| | outputs=[msg_input] |
| | ) |
| | |
| | |
| | def create_sample_click(text): |
| | def handler(history): |
| | for result in chat_respond(text, history or []): |
| | yield result |
| | return handler |
| | |
| | sample_btn1.click(fn=create_sample_click("κ°λ¨μμ μΉ΄ν μ°½μ
νλ €λ©΄ μ΄λ»κ² ν΄μΌ νλμ?"), inputs=[chatbot], outputs=[chatbot]) |
| | sample_btn2.click(fn=create_sample_click("μΉν¨μ§μ΄ κ°μ₯ ν¬νλ μ§μμ μ΄λμΈκ°μ?"), inputs=[chatbot], outputs=[chatbot]) |
| | sample_btn3.click(fn=create_sample_click("1μΈ΅μ΄ μ 리ν μ
μ’
μ 무μμΈκ°μ?"), inputs=[chatbot], outputs=[chatbot]) |
| | sample_btn4.click(fn=create_sample_click("νλμ°¨μ΄μ¦ μ μ μ¨μ΄ λμ μ
μ’
μ?"), inputs=[chatbot], outputs=[chatbot]) |
| |
|
| |
|
| | |
| | if __name__ == "__main__": |
| | demo.launch(server_name="0.0.0.0", server_port=7860, share=False) |