Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files
README.md
CHANGED
|
@@ -4,12 +4,13 @@ emoji: "🤖"
|
|
| 4 |
colorFrom: "purple"
|
| 5 |
colorTo: "blue"
|
| 6 |
sdk: "gradio"
|
| 7 |
-
sdk_version: "4.44.
|
| 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
|
| 15 |
-
|
|
|
|
|
|
| 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,
|
| 2 |
import gradio as gr
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
|
| 5 |
-
|
| 6 |
-
# 환경 변수 로드
|
| 7 |
load_dotenv(os.path.join(os.path.dirname(__file__), "..", "backend", ".env"), override=True)
|
| 8 |
|
| 9 |
-
|
|
|
|
| 10 |
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
gr.Markdown('## PersonaMate Pro — OAuth 수집 + 추천 UI')
|
| 13 |
with gr.Row():
|
| 14 |
with gr.Column(scale=1):
|
| 15 |
gr.Markdown('### 1) OAuth 로그인')
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 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 팔로잉
|
| 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(
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 52 |
-
|
| 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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|