Marathon23's picture
Update app.py
87b9399 verified
import os
import openai
import gradio as gr
import gspread
from google.oauth2.service_account import Credentials
from datetime import datetime
import plotly.express as px
# 從環境變數中取得 OpenAI API key
openai.api_key = os.environ.get("OenAI_API")
if not openai.api_key:
raise ValueError("OenAI_API not found. Please set it in your Hugging Face Space secret settings.")
# 讀取Google Service Account憑證檔案並授權
creds = Credentials.from_service_account_file("service_account.json", scopes=["https://www.googleapis.com/auth/spreadsheets"])
gc = gspread.authorize(creds)
# 請替換為您實際的 Google 試算表 URL
SHEET_URL = "https://docs.google.com/spreadsheets/d/1qS6EBih-icMAXoMLMNObFsT6-WJYD8fQPfN8xSRukPY/edit?usp=sharing"
sheet = gc.open_by_url(SHEET_URL).sheet1
user_data = {}
mbti_questions = [
{"question": "你在一個熱鬧的派對中,通常傾向於:",
"options": ["積極認識新朋友", "與少數熟識的人安靜交流"],
"dimension": "EI"},
{"question": "週末時,你比較喜歡:",
"options": ["參加團體活動", "獨自從事個人嗜好"],
"dimension": "EI"},
{"question": "當你獲得一項新計畫的資訊時,你通常會先:",
"options": ["注意可用的實際細節", "思考其未來可能的影響"],
"dimension": "SN"},
{"question": "在學習新知識時,你比較傾向:",
"options": ["先掌握具體事實", "探索概念與模式"],
"dimension": "SN"},
{"question": "在解決問題時,你較重視:",
"options": ["實際可行的解決方案", "創造性的替代方案"],
"dimension": "SN"},
{"question": "當需要做決定時,你比較重視:",
"options": ["邏輯與客觀分析", "個人價值與感受"],
"dimension": "TF"},
{"question": "當朋友向你求助,你更傾向於:",
"options": ["理性分析問題並給出建議", "先同理其感受並給予情緒支持"],
"dimension": "TF"},
{"question": "在職場環境中,你通常更欣賞:",
"options": ["公平而合理的決策過程", "關懷並顧及員工感受的氛圍"],
"dimension": "TF"},
{"question": "當規劃假期旅行時,你偏好:",
"options": ["提前制定詳細行程", "保持彈性隨機應變"],
"dimension": "JP"},
{"question": "在處理日常事務時,你比較傾向:",
"options": ["結構化、按步驟進行", "即興而行、享受變化"],
"dimension": "JP"}
]
question_index = 0
e_count, i_count = 0, 0
s_count, n_count = 0, 0
t_count, f_count = 0, 0
j_count, p_count = 0, 0
def gpt_recommendation(input_content, user_language):
if user_language == '繁體中文':
system_content = (
"你是一位熟悉MBTI人格測驗和相關知識的酒吧助理,"
"精通世界知名的經典調酒,且能推薦不同的經典調酒"
"推薦的經典調酒,一定要是真的存在於世界上"
"你只能回答關於「酒類、口味、MBTI人格」三大類問題與推薦"
"一定要快速介紹這個 MBTI 的人格特質,和適合的工作、適合朋友(列舉兩種MBTI性格)、適合戀愛對象(列舉兩種MBTI性格)"
"你也擅長數據分析,會將調酒內的各項成分比例清楚寫出來,請務必在推薦中,將每種成分的毫升數列成多行格式。例如:"
"- 琴酒:30毫升\n- 甜苦艾酒:30毫升\n- 金巴利:30毫升"
"專業且友善地推薦適合的調酒。"
"介紹這杯調酒的故事和口感,以及為什麼代表你的MBTI。"
"針對每個MBTI的結果,做個興趣分析(共5個),方便調酒師與客人能開啟聊天用。"
"並以繁體中文回應。"
)
else:
system_content = (
"You are a bar assistant familiar with the MBTI personality test and related knowledge."
"You are proficient in world-renowned classic cocktails and can recommend different classic cocktails."
"The recommended cocktails must be real and exist in the world."
"You can only respond to three types of questions and recommendations: ‘alcohol, flavors, and MBTI personalities.‘"
"You are also skilled in data analysis and will clearly outline the ingredient ratios for each recommended cocktail. Please ensure to list the volume of each ingredient in milliliters in a multi-line format, for example:"
"- Gin: 30ml\n- Sweet Vermouth: 30ml\n- Campari: 30ml"
"Provide professional and friendly recommendations for suitable cocktails."
"For each MBTI result, conduct an interest analysis (a total of 5 points) to help bartenders and guests initiate conversations."
"Respond in English."
)
messages = [
{"role": "system", "content": system_content},
{"role": "user", "content": input_content}
]
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=messages,
temperature=0.7,
max_tokens=2048
)
recommendation = response.choices[0].message['content']
except Exception as e:
recommendation = f"調用 GPT-4o 失敗:{e}" if user_data.get('language', '繁體中文') == '繁體中文' else f"GPT-4 call failed: {e}"
return recommendation
def language_selection(language):
user_data['language'] = language
if language == '繁體中文':
return (gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
"請輸入您的手機號碼:",
"請輸入您的暱稱或姓名:",
gr.update(visible=False))
else:
return (gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=True),
"Please enter your phone number:",
"Please enter your nickname or name:",
gr.update(visible=False))
def collect_user_info(phone, name):
user_data['phone'] = phone
user_data['name'] = name
if not phone or not name:
if user_data['language'] == '繁體中文':
return (gr.update(visible=True), "請輸入完整的手機號碼和暱稱或姓名。", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False))
else:
return (gr.update(visible=True), "Please enter both your phone number and nickname or name.", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False))
options = ["我想要做個MBTI人格測驗","直接告訴您,我的MBTI人格,麻煩你推薦調酒給我","直接點酒,想喝的口感或風味?"] if user_data['language'] == '繁體中文' else [
"I want to do an MBTI personality test",
"I will directly tell you my MBTI personality type, please recommend a cocktail to me",
"Order a drink directly, what taste or flavor do you want?"
]
return (gr.update(choices=options, visible=True), "", gr.update(visible=False), gr.update(visible=True), gr.update(visible=False))
def option_selection(selected_option):
user_data['selected_option'] = selected_option
if selected_option in ["我想要做個MBTI人格測驗", "I want to do an MBTI personality test"]:
return (f"測驗開始!請回答以下問題:\n{mbti_questions[0]['question']}",
gr.update(choices=mbti_questions[0]["options"], visible=True),
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False))
elif selected_option in ["直接告訴您,我的MBTI人格,麻煩你推薦調酒給我", "I will directly tell you my MBTI personality type, please recommend a cocktail to me"]:
prompt = "請輸入您的MBTI類型(例如:INTJ):" if user_data['language'] == '繁體中文' else "Please enter your MBTI type (e.g., INTJ):"
return (prompt,
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=True), # 顯示 MBTI類型輸入框
gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=False))
else:
prompt = "請在下方格子輸入想喝的口感或風味描述(例如:清爽的、柑橘味、微甜、酒感重)" if user_data['language'] == '繁體中文' else "Please enter the taste or flavor you would like (e.g., refreshing, citrus, slightly sweet):"
return (prompt,
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=False))
def mbti_test(answer):
global question_index, e_count, i_count, s_count, n_count, t_count, f_count, j_count, p_count
current_question = mbti_questions[question_index]
dim = current_question["dimension"]
index_chosen = current_question["options"].index(answer)
if dim == "EI":
e_count += (1 if index_chosen == 0 else 0)
i_count += (1 if index_chosen == 1 else 0)
elif dim == "SN":
s_count += (1 if index_chosen == 0 else 0)
n_count += (1 if index_chosen == 1 else 0)
elif dim == "TF":
t_count += (1 if index_chosen == 0 else 0)
f_count += (1 if index_chosen == 1 else 0)
elif dim == "JP":
j_count += (1 if index_chosen == 0 else 0)
p_count += (1 if index_chosen == 1 else 0)
question_index += 1
if question_index < len(mbti_questions):
next_question = mbti_questions[question_index]
return (f"問題 {question_index + 1}/{len(mbti_questions)}:\n{next_question['question']}",
gr.update(choices=next_question["options"], visible=True),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=True))
else:
mbti_result = (
("E" if e_count >= i_count else "I") +
("S" if s_count >= n_count else "N") +
("T" if t_count >= f_count else "F") +
("J" if j_count >= p_count else "P")
)
question_index = 0
e_count = i_count = s_count = n_count = t_count = f_count = j_count = p_count = 0
user_data["mbti_result"] = mbti_result
input_content = f"My MBTI type is {mbti_result}. Recommend a suitable cocktail..."
recommendation = gpt_recommendation(input_content, user_data['language'])
# 全部答題完成並產生推薦後,隱藏 btn_answer
return (f"測驗完成!您的 MBTI 類型為:{mbti_result}\n調酒推薦:\n{recommendation}\n\n請為MBTI準確度及AI推薦調酒進行評分(1到10分,最滿意為10分):",
gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=False))
def direct_mbti_recommendation(mbti_type):
user_data["mbti_result"] = mbti_type.upper()
input_content = f"My MBTI type is {user_data['mbti_result']}. Recommend a suitable cocktail..."
recommendation = gpt_recommendation(input_content, user_data['language'])
return (f"您的 MBTI 類型為:{user_data['mbti_result']}\n調酒推薦:\n{recommendation}\n\n請為MBTI準確度及AI推薦調酒進行評分(1到10分,最滿意為10分):",
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=False))
def flavor_recommendation(flavor):
user_data["flavor"] = flavor
input_content = f"I want a cocktail with a {flavor} flavor..."
recommendation = gpt_recommendation(input_content, user_data['language'])
return (f"您想要的口感是:{flavor}\n調酒推薦:\n{recommendation}\n\n請為MBTI準確度及AI推薦調酒進行評分(1到10分,最滿意為10分):",
gr.update(visible=True),
gr.update(visible=True),
gr.update(visible=False))
def submit_rating(rating, assistant_text):
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
phone = user_data.get('phone', '')
name = user_data.get('name', '')
mbti_result = user_data.get('mbti_result', '')
sheet.append_row([now, phone, name, mbti_result, rating, assistant_text])
if user_data['language'] == '繁體中文':
return "謝謝你給予寶貴的意見", gr.update(visible=False), gr.update(visible=False)
else:
return "Thanks for the feedback", gr.update(visible=False), gr.update(visible=False)
def parse_cocktail_ratios(assistant_text):
lines = assistant_text.split('\n')
ratios = {}
found = False
for line in lines:
line_lower = line.lower()
if ('毫升' in line_lower or '%' in line_lower or 'ml' in line_lower):
found = True
item = (line.strip()
.replace(':', ':')
.replace('毫升', '')
.replace('ml', '')
.replace('%', ''))
parts = item.split(':')
if len(parts) >= 2:
ingredient = parts[0].strip("- ").strip()
try:
ratio_value = float(parts[1].strip())
if ratio_value <= 0:
continue
ratios[ingredient] = ratio_value
except:
pass
if not found or not ratios:
return None
return ratios
def generate_pie_chart(assistant_text):
ratios = parse_cocktail_ratios(assistant_text)
if not ratios:
fig = px.scatter(title="尚未偵測到正式的調酒配方比例")
return fig
ingredients = list(ratios.keys())
values = list(ratios.values())
fig = px.pie(values=values, names=ingredients, title="調酒配方圓餅圖")
return fig
with gr.Blocks() as demo:
language = gr.Radio(choices=["繁體中文", "English"], label="選擇介面語言 / Choose Interface Language")
btn_language = gr.Button("確認 / Confirm")
phone_input = gr.Textbox(label="", visible=False)
name_input = gr.Textbox(label="", visible=False)
btn_user_info = gr.Button("提交 / Submit", visible=False)
option = gr.Radio(choices=[], label="", visible=False)
btn_option = gr.Button("確認 / Confirm", visible=False)
# 使「酒吧助理回應」框互動為False,即唯讀不可輸入
assistant_output = gr.Textbox(label="酒吧助理回應 / Assistant Response", visible=True, interactive=False)
answer = gr.Radio(choices=[], label="選擇答案 / Choose an Answer:", visible=False)
btn_answer = gr.Button("提交答案 / Submit Answer", visible=False)
mbti_input = gr.Textbox(label="請在下方輸入您的MBTI類型,大寫英文", visible=False)
btn_mbti_input = gr.Button("提交 MBTI 類型", visible=False)
flavor_input = gr.Textbox(label="請在下方輸入口感或風味描述", visible=False)
btn_flavor_input = gr.Button("提交口感或風味", visible=False)
rating = gr.Slider(minimum=1, maximum=10, step=1, label="請為MBTI準確度及AI推薦調酒進行評分(1到10分,最滿意為10分)", visible=False)
btn_rating_submit = gr.Button("送出", visible=False)
chart_output = gr.Plot(label="調酒配方圓餅圖", visible=True)
btn_language.click(
language_selection,
inputs=language,
outputs=[phone_input, name_input, btn_user_info, phone_input, name_input, btn_language]
)
btn_user_info.click(
collect_user_info,
inputs=[phone_input, name_input],
outputs=[option, assistant_output, phone_input, btn_option, btn_user_info]
)
btn_option.click(
option_selection,
inputs=option,
outputs=[assistant_output, answer, btn_answer, mbti_input, flavor_input, btn_mbti_input, btn_flavor_input, btn_option]
)
btn_answer.click(
mbti_test,
inputs=answer,
outputs=[assistant_output, answer, rating, btn_rating_submit, btn_answer]
)
btn_mbti_input.click(
direct_mbti_recommendation,
inputs=mbti_input,
outputs=[assistant_output, rating, btn_rating_submit, btn_mbti_input]
)
btn_flavor_input.click(
flavor_recommendation,
inputs=flavor_input,
outputs=[assistant_output, rating, btn_rating_submit, btn_flavor_input]
)
btn_rating_submit.click(
submit_rating,
inputs=[rating, assistant_output],
outputs=[assistant_output, rating, btn_rating_submit]
)
assistant_output.change(
fn=generate_pie_chart,
inputs=assistant_output,
outputs=chart_output
)
demo.launch(server_name="0.0.0.0", server_port=7860)