kimddol commited on
Commit
b97f664
·
verified ·
1 Parent(s): 29e1d84

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. README.md +4 -3
  2. app.py +54 -67
README.md CHANGED
@@ -4,12 +4,13 @@ emoji: "🤖"
4
  colorFrom: "purple"
5
  colorTo: "blue"
6
  sdk: "gradio"
7
- sdk_version: "4.44.0"
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
13
 
14
- This Space is built with Gradio and uses `frontend/app.py` as the entry point.
15
- Dependencies are listed in `frontend/requirements.txt`.
 
 
4
  colorFrom: "purple"
5
  colorTo: "blue"
6
  sdk: "gradio"
7
+ sdk_version: "4.44.1"
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
13
 
14
+ This Space uses FastAPI to serve the Gradio UI mounted at `/`.
15
+ The entrypoint is `app.py`, which defines both the API server and the Gradio interface.
16
+ Dependencies are listed in `requirements.txt`.
app.py CHANGED
@@ -1,87 +1,74 @@
1
- import os, json, requests
2
  import gradio as gr
3
  from dotenv import load_dotenv
4
 
5
- from fastapi import FastAPI
6
- # 환경 변수 로드
7
  load_dotenv(os.path.join(os.path.dirname(__file__), "..", "backend", ".env"), override=True)
8
 
9
- BACKEND = os.getenv('BACKEND_URL','https://your-vercel-backend.vercel.app')
 
10
 
11
- with gr.Blocks(title='PersonaMate Pro (OAuth + Simplified UI)') as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  gr.Markdown('## PersonaMate Pro — OAuth 수집 + 추천 UI')
13
  with gr.Row():
14
  with gr.Column(scale=1):
15
  gr.Markdown('### 1) OAuth 로그인')
16
- google_html = gr.HTML(f'<a href="{BACKEND}/oauth/google/start" target="_blank">Google (YouTube) 로그인 열기</a>')
17
- instagram_html = gr.HTML(f'<a href="{BACKEND}/oauth/instagram/start" target="_blank">Instagram 로그인 열기</a>')
18
- x_html = gr.HTML(f'<a href="{BACKEND}/oauth/x/start" target="_blank">X (Twitter) 로그인 열기</a>')
19
  with gr.Column(scale=2):
20
  gr.Markdown('### 2) 자동 수집')
21
- yt_chk=gr.Checkbox(label='YouTube 구독 목록 사용', value=True)
22
- ig_chk=gr.Checkbox(label='Instagram 해시태그 사용', value=False)
23
- x_chk=gr.Checkbox(label='X 팔로잉 사용자명 사용', value=False)
24
- fetch_btn=gr.Button('내 계정에서 데이터 수집')
25
- fetch_result=gr.JSON(label="수집된 데이터 미리보기")
26
  with gr.Row():
27
  with gr.Column(scale=1):
28
  gr.Markdown('### 3) 입력/MBTI')
29
- yt_text=gr.Textbox(lines=6,label='유튜브 구독 (수집/수동 혼용)')
30
- sns_text=gr.Textbox(lines=6,label='SNS 키워드/계정 (수집/수동 혼용)')
31
- mbti=gr.Dropdown(choices=['ISTJ','ISFJ','INFJ','INTJ','ISTP','ISFP','INFP','INTP','ESTP','ESFP','ENFP','ENTP','ESTJ','ESFJ','ENFJ','ENTJ'], value='ENFP', label='MBTI')
32
- use_openai=gr.Checkbox(label='OpenAI 임베딩 사용', value=True)
33
- run_btn=gr.Button('분석 & 추천 실행', variant='primary')
34
- send_email_btn=gr.Button("추천 결과 이메일로 보내기 (Gmail)")
 
 
 
35
  with gr.Column(scale=3):
36
  gr.Markdown('### 4) 추천 결과')
37
- result_table=gr.Dataframe(headers=["채널 이름","사이트 주소"], row_count=10, col_count=2)
38
 
39
- # 버튼 동작 연결 ��거 (Hugging Face에서는 webbrowser.open 사용 불가)
40
- # 대신 gr.Link 컴포넌트로 대체
41
-
42
- def fetch_data_fn():
43
- try:
44
- res = requests.get(f"{BACKEND}/fetch_data", timeout=60)
45
- res.raise_for_status()
46
- return res.json()
47
- except Exception as e:
48
- return {"error": str(e)}
49
  fetch_btn.click(fetch_data_fn, inputs=[], outputs=[fetch_result])
 
50
 
51
- def _run(yt, sns, mbti, use_openai):
52
- try:
53
- payload = {
54
- "youtube_subscriptions": [s.strip() for s in yt.splitlines() if s.strip()],
55
- "sns_keywords": [s.strip() for s in sns.splitlines() if s.strip()],
56
- "mbti": mbti
57
- }
58
- res = requests.post(f"{BACKEND}/youtube/recommendations", json=payload, timeout=120)
59
- res.raise_for_status()
60
- data = res.json().get("recommendations", {})
61
- except Exception as e:
62
- data = {"youtube":[{"name":"추천 실패","url":str(e)}], "web":[]}
63
-
64
- rows = []
65
- youtube_list = data.get("youtube", [])
66
- web_list = data.get("web", [])
67
- if not youtube_list and not web_list:
68
- # fallback: 최소한 1개라도 표시
69
- youtube_list = [{"name":"추천 실패","url":"http://youtube.com"}]
70
- web_list = [{"name":"추천 실패","url":"http://example.com"}]
71
-
72
- for c in youtube_list + web_list:
73
- rows.append([
74
- c.get("name",""),
75
- c.get("url","")
76
- ])
77
- return rows
78
-
79
- run_btn.click(_run, [yt_text, sns_text, mbti, use_openai], [result_table])
80
-
81
- # Vercel 배포를 위해 FastAPI 앱을 생성하고 Gradio 앱을 마운트합니다.
82
- # Vercel은 이 'app' 객체를 찾아 서버리스 함수로 실행합니다.
83
- app = FastAPI()
84
- app = gr.mount_gradio_app(app, demo, path="/")
85
-
86
- # if __name__=='__main__':
87
- # demo.launch()
 
1
+ import os, requests
2
  import gradio as gr
3
  from dotenv import load_dotenv
4
 
5
+ # 환경 변수 로드 (backend/.env에는 Vercel 배포용 환경이 포함되어 있어야 합니다)
 
6
  load_dotenv(os.path.join(os.path.dirname(__file__), "..", "backend", ".env"), override=True)
7
 
8
+ # Vercel 배포된 백엔드 URL (GitHub Space Secrets에 BACKEND_URL 설정)
9
+ BACKEND = os.getenv('BACKEND_URL', 'https://your-vercel-backend.vercel.app')
10
 
11
+ def fetch_data_fn():
12
+ try:
13
+ res = requests.get(f"{BACKEND}/fetch_data", timeout=60)
14
+ res.raise_for_status()
15
+ return res.json()
16
+ except Exception as e:
17
+ return {"error": str(e)}
18
+
19
+ def run_recommendations(yt, sns, mbti, use_openai):
20
+ try:
21
+ payload = {
22
+ "youtube_subscriptions": [s.strip() for s in yt.splitlines() if s.strip()],
23
+ "sns_keywords": [s.strip() for s in sns.splitlines() if s.strip()],
24
+ "mbti": mbti
25
+ }
26
+ res = requests.post(f"{BACKEND}/youtube/recommendations", json=payload, timeout=120)
27
+ res.raise_for_status()
28
+ data = res.json().get("recommendations", {})
29
+ except Exception as e:
30
+ data = {"youtube": [{"name": "추천 실패", "url": str(e)}], "web": []}
31
+
32
+ rows = []
33
+ for c in data.get("youtube", []) + data.get("web", []):
34
+ rows.append([c.get("name", ""), c.get("url", "")])
35
+ if not rows:
36
+ rows = [["추천 실패", ""]]
37
+ return rows
38
+
39
+ with gr.Blocks(title='PersonaMate Pro — OAuth 수집 + 추천 UI') as demo:
40
  gr.Markdown('## PersonaMate Pro — OAuth 수집 + 추천 UI')
41
  with gr.Row():
42
  with gr.Column(scale=1):
43
  gr.Markdown('### 1) OAuth 로그인')
44
+ gr.HTML(f'<a href="{BACKEND}/oauth/google/start" target="_blank">Google (YouTube) 로그인</a>')
45
+ gr.HTML(f'<a href="{BACKEND}/oauth/instagram/start" target="_blank">Instagram 로그인</a>')
46
+ gr.HTML(f'<a href="{BACKEND}/oauth/x/start" target="_blank">X 로그인</a>')
47
  with gr.Column(scale=2):
48
  gr.Markdown('### 2) 자동 수집')
49
+ yt_chk = gr.Checkbox(label='YouTube 구독 목록 사용', value=True)
50
+ ig_chk = gr.Checkbox(label='Instagram 해시태그 사용', value=False)
51
+ x_chk = gr.Checkbox(label='X 팔로잉 리스트 사용', value=False)
52
+ fetch_btn = gr.Button('데이터 수집')
53
+ fetch_result = gr.JSON(label="수집된 데이터 미리보기")
54
  with gr.Row():
55
  with gr.Column(scale=1):
56
  gr.Markdown('### 3) 입력/MBTI')
57
+ yt_text = gr.Textbox(lines=6, label='유튜브 구독 (수집/수동)')
58
+ sns_text = gr.Textbox(lines=6, label='SNS 키워드/계정')
59
+ mbti = gr.Dropdown(
60
+ choices=['ISTJ','ISFJ','INFJ','INTJ','ISTP','ISFP','INFP','INTP','ESTP','ESFP','ENFP','ENTP','ESTJ','ESFJ','ENFJ','ENTJ'],
61
+ value='ENFP',
62
+ label='MBTI'
63
+ )
64
+ use_openai = gr.Checkbox(label='OpenAI 임베딩 사용', value=True)
65
+ run_btn = gr.Button('추천 실행', variant='primary')
66
  with gr.Column(scale=3):
67
  gr.Markdown('### 4) 추천 결과')
68
+ result_table = gr.Dataframe(headers=["채널 이름", "사이트 주소"], row_count=10, col_count=2)
69
 
 
 
 
 
 
 
 
 
 
 
70
  fetch_btn.click(fetch_data_fn, inputs=[], outputs=[fetch_result])
71
+ run_btn.click(run_recommendations, [yt_text, sns_text, mbti, use_openai], [result_table])
72
 
73
+ if __name__ == '__main__':
74
+ demo.launch()