Spaces:
Sleeping
Sleeping
with gr.Tab("飛特音速版"):
Browse files- app.py +170 -7
- requirements.txt +2 -2
app.py
CHANGED
|
@@ -635,6 +635,7 @@ def process_youtube_link(password, link):
|
|
| 635 |
formatted_transcript_json, \
|
| 636 |
summary, \
|
| 637 |
key_moments_html, \
|
|
|
|
| 638 |
mind_map, \
|
| 639 |
mind_map_html, \
|
| 640 |
html_content, \
|
|
@@ -1766,6 +1767,104 @@ def poll_run_status(run_id, thread_id, timeout=600, poll_interval=5):
|
|
| 1766 |
|
| 1767 |
return run.status
|
| 1768 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1769 |
# --- Slide mode ---
|
| 1770 |
def update_slide(direction):
|
| 1771 |
global TRANSCRIPTS
|
|
@@ -1794,6 +1893,8 @@ def prev_slide():
|
|
| 1794 |
def next_slide():
|
| 1795 |
return update_slide(1)
|
| 1796 |
|
|
|
|
|
|
|
| 1797 |
def init_params(text, request: gr.Request):
|
| 1798 |
if request:
|
| 1799 |
print("Request headers dictionary:", request.headers)
|
|
@@ -1826,6 +1927,16 @@ def init_params(text, request: gr.Request):
|
|
| 1826 |
|
| 1827 |
return admin, reading_passage_admin, summary_admin, see_detail, password_text, youtube_link
|
| 1828 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1829 |
HEAD = """
|
| 1830 |
<meta charset="UTF-8">
|
| 1831 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
@@ -1890,6 +2001,17 @@ HEAD = """
|
|
| 1890 |
</script>
|
| 1891 |
"""
|
| 1892 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1893 |
with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, secondary_hue=gr.themes.colors.amber, text_size = gr.themes.sizes.text_lg), head=HEAD) as demo:
|
| 1894 |
with gr.Row() as admin:
|
| 1895 |
password = gr.Textbox(label="Password", type="password", elem_id="password_input", visible=True)
|
|
@@ -1899,9 +2021,14 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
|
|
| 1899 |
web_link = gr.Textbox(label="Enter Web Page Link", visible=False)
|
| 1900 |
user_data = gr.Textbox(label="User Data", elem_id="user_data_input", visible=True)
|
| 1901 |
youtube_link_btn = gr.Button("Submit_YouTube_Link", elem_id="youtube_link_btn", visible=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1902 |
with gr.Tab("AI小精靈"):
|
| 1903 |
with gr.Row():
|
| 1904 |
-
with gr.Tab("飛特"):
|
| 1905 |
bot_avatar = "https://junyi-avatar.s3.ap-northeast-1.amazonaws.com/live/%20%20foxcat-star-18.png?v=20231113095823614"
|
| 1906 |
user_avatar = "https://junyitopicimg.s3.amazonaws.com/s4byy--icon.jpe?v=20200513013523726"
|
| 1907 |
latex_delimiters = [{"left": "$", "right": "$", "display": False}]
|
|
@@ -1939,6 +2066,17 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
|
|
| 1939 |
ai_chatbot = gr.Chatbot(avatar_images=[bot_avatar, user_avatar], label="ai_chatbot", show_share_button=False, likeable=True, show_label=False)
|
| 1940 |
ai_msg = gr.Textbox(label="Message")
|
| 1941 |
ai_send_button = gr.Button("Send", variant="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1942 |
with gr.Tab("文章模式"):
|
| 1943 |
with gr.Row() as reading_passage_admin:
|
| 1944 |
reading_passage_kind = gr.Textbox(value="reading_passage", show_label=False)
|
|
@@ -1962,6 +2100,17 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
|
|
| 1962 |
with gr.Tab("關鍵時刻"):
|
| 1963 |
with gr.Row():
|
| 1964 |
key_moments_html = gr.HTML(value="")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1965 |
with gr.Tab("教學備課"):
|
| 1966 |
with gr.Row():
|
| 1967 |
content_subject = gr.Dropdown(label="選擇主題", choices=["數學", "自然", "國文", "英文", "社會","物理", "化學", "生物", "地理", "歷史", "公民"], value="", visible=False)
|
|
@@ -2136,7 +2285,8 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
|
|
| 2136 |
file_upload.change(process_file, inputs=file_upload, outputs=[btn_1, btn_2, btn_3, df_summarise, df_string_output])
|
| 2137 |
|
| 2138 |
# 当输入 YouTube 链接时触发
|
| 2139 |
-
|
|
|
|
| 2140 |
video_id,
|
| 2141 |
btn_1,
|
| 2142 |
btn_2,
|
|
@@ -2144,6 +2294,7 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
|
|
| 2144 |
df_string_output,
|
| 2145 |
df_summarise,
|
| 2146 |
key_moments_html,
|
|
|
|
| 2147 |
mind_map,
|
| 2148 |
mind_map_html,
|
| 2149 |
transcript_html,
|
|
@@ -2154,17 +2305,29 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
|
|
| 2154 |
content_subject,
|
| 2155 |
content_grade,
|
| 2156 |
]
|
|
|
|
|
|
|
|
|
|
| 2157 |
youtube_link.change(
|
| 2158 |
process_youtube_link,
|
| 2159 |
-
inputs=
|
| 2160 |
-
outputs=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2161 |
)
|
| 2162 |
-
|
| 2163 |
youtube_link_btn.click(
|
| 2164 |
process_youtube_link,
|
| 2165 |
-
inputs=
|
| 2166 |
-
outputs=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2167 |
)
|
|
|
|
| 2168 |
|
| 2169 |
# 当输入网页链接时触发
|
| 2170 |
# web_link.change(process_web_link, inputs=web_link, outputs=[btn_1, btn_2, btn_3, df_summarise, df_string_output])
|
|
|
|
| 635 |
formatted_transcript_json, \
|
| 636 |
summary, \
|
| 637 |
key_moments_html, \
|
| 638 |
+
key_moments, \
|
| 639 |
mind_map, \
|
| 640 |
mind_map_html, \
|
| 641 |
html_content, \
|
|
|
|
| 1767 |
|
| 1768 |
return run.status
|
| 1769 |
|
| 1770 |
+
def streaming_chat_with_open_ai(user_message, chat_history, password, thread_id, trascript, content_subject, content_grade):
|
| 1771 |
+
verify_password(password)
|
| 1772 |
+
|
| 1773 |
+
print("===streaming_chat_with_open_ai===")
|
| 1774 |
+
print(thread_id)
|
| 1775 |
+
|
| 1776 |
+
# 先計算 user_message 是否超過 500 個字
|
| 1777 |
+
if len(user_message) > 1500:
|
| 1778 |
+
error_msg = "你的訊息太長了,請縮短訊息長度至五百字以內"
|
| 1779 |
+
raise gr.Error(error_msg)
|
| 1780 |
+
|
| 1781 |
+
# 如果 chat_history 超過 10 則訊息,直接 return "對話超過上限"
|
| 1782 |
+
if chat_history is not None and len(chat_history) > 10:
|
| 1783 |
+
error_msg = "此次對話超過上限"
|
| 1784 |
+
raise gr.Error(error_msg)
|
| 1785 |
+
|
| 1786 |
+
# fake data
|
| 1787 |
+
socratic_mode = True
|
| 1788 |
+
|
| 1789 |
+
try:
|
| 1790 |
+
assistant_id = "asst_kmvZLNkDUYaNkMNtZEAYxyPq"
|
| 1791 |
+
client = OPEN_AI_CLIENT
|
| 1792 |
+
# 直接安排逐字稿資料 in instructions
|
| 1793 |
+
trascript_json = json.loads(trascript)
|
| 1794 |
+
# 移除 embed_url, screenshot_path
|
| 1795 |
+
for entry in trascript_json:
|
| 1796 |
+
entry.pop('embed_url', None)
|
| 1797 |
+
entry.pop('screenshot_path', None)
|
| 1798 |
+
trascript_text = json.dumps(trascript_json, ensure_ascii=False, indent=2)
|
| 1799 |
+
# trascript_text 移除 \n, 空白
|
| 1800 |
+
trascript_text = trascript_text.replace("\n", "").replace(" ", "")
|
| 1801 |
+
|
| 1802 |
+
instructions = f"""
|
| 1803 |
+
科目:{content_subject}
|
| 1804 |
+
年級:{content_grade}
|
| 1805 |
+
逐字稿資料:{trascript_text}
|
| 1806 |
+
-------------------------------------
|
| 1807 |
+
你是一個專業的{content_subject}老師, user 為{content_grade}的學生
|
| 1808 |
+
socratic_mode = {socratic_mode}
|
| 1809 |
+
if socratic_mode is True,
|
| 1810 |
+
- 請用蘇格拉底式的提問方式,引導學生思考,並且給予學生一些提示
|
| 1811 |
+
- 一次只問一個問題,字數在100字以內
|
| 1812 |
+
- 不要直接給予答案,讓學生自己思考
|
| 1813 |
+
- 但可以給予一些提示跟引導,例如給予影片的時間軸,讓學生自己去找答案
|
| 1814 |
+
|
| 1815 |
+
if socratic_mode is False,
|
| 1816 |
+
- 直接回答學生問題,字數在100字以內
|
| 1817 |
+
|
| 1818 |
+
rule:
|
| 1819 |
+
- 請一定要用繁體中文回答 zh-TW,並用台灣人的口語表達,回答時不用特別說明這是台灣人的語氣,也不用說這是「台語的說法」
|
| 1820 |
+
- 不用提到「逐字稿」這個詞,用「內容」代替
|
| 1821 |
+
- 如果學生問了一些問題你無法判斷,請告訴學生你無法判斷,並建議學生可以問其他問題
|
| 1822 |
+
- 或者你可以反問學生一些問題,幫助學生更好的理解資料,字數在100字以內
|
| 1823 |
+
- 如果學生的問題與資料文本無關,請告訴學生你「無法回答超出影片範圍的問題」,並告訴他可以怎麼問什麼樣的問題(一個就好)
|
| 1824 |
+
- 只要是參考逐字稿資料,請在回答的最後標註【參考資料:(分):(秒)】
|
| 1825 |
+
- 回答範圍一定要在逐字稿資料內,不要引用其他資料,請嚴格執行
|
| 1826 |
+
- 並在重複問句後給予學生鼓勵,讓學生有學習的動力
|
| 1827 |
+
- 請用 {content_grade} 的學生能懂的方式回答
|
| 1828 |
+
"""
|
| 1829 |
+
|
| 1830 |
+
# 创建线程
|
| 1831 |
+
if not thread_id:
|
| 1832 |
+
thread = client.beta.threads.create()
|
| 1833 |
+
thread_id = thread.id
|
| 1834 |
+
print(f"new thread_id: {thread_id}")
|
| 1835 |
+
else:
|
| 1836 |
+
thread = client.beta.threads.retrieve(thread_id)
|
| 1837 |
+
print(f"old thread_id: {thread_id}")
|
| 1838 |
+
|
| 1839 |
+
# 向线程添加用户的消息
|
| 1840 |
+
client.beta.threads.messages.create(
|
| 1841 |
+
thread_id=thread.id,
|
| 1842 |
+
role="user",
|
| 1843 |
+
content=user_message + "/n (請一定要用繁體中文回答 zh-TW,並用台灣人的禮貌口語表達,回答時不要特別說明這是台灣人的語氣,不用提到「逐字稿」這個詞,用「內容」代替)),請在回答的最後標註【參考資料:(時):(分):(秒)】,(如果是反問學生,就只問一個問題,請幫助學生更好的理解資料,字數在100字以內)"
|
| 1844 |
+
)
|
| 1845 |
+
|
| 1846 |
+
with client.beta.threads.runs.stream(
|
| 1847 |
+
thread_id=thread.id,
|
| 1848 |
+
assistant_id=assistant_id,
|
| 1849 |
+
instructions=instructions,
|
| 1850 |
+
) as stream:
|
| 1851 |
+
partial_messages = ""
|
| 1852 |
+
for event in stream:
|
| 1853 |
+
if event.data and event.data.object == "thread.message.delta":
|
| 1854 |
+
message = event.data.delta.content[0].text.value
|
| 1855 |
+
partial_messages += message
|
| 1856 |
+
yield partial_messages
|
| 1857 |
+
|
| 1858 |
+
except Exception as e:
|
| 1859 |
+
print(f"Error: {e}")
|
| 1860 |
+
raise gr.Error(f"Error: {e}")
|
| 1861 |
+
|
| 1862 |
+
def create_thread_id():
|
| 1863 |
+
thread = OPEN_AI_CLIENT.beta.threads.create()
|
| 1864 |
+
thread_id = thread.id
|
| 1865 |
+
print(f"create new thread_id: {thread_id}")
|
| 1866 |
+
return thread_id
|
| 1867 |
+
|
| 1868 |
# --- Slide mode ---
|
| 1869 |
def update_slide(direction):
|
| 1870 |
global TRANSCRIPTS
|
|
|
|
| 1893 |
def next_slide():
|
| 1894 |
return update_slide(1)
|
| 1895 |
|
| 1896 |
+
|
| 1897 |
+
# --- Init params ---
|
| 1898 |
def init_params(text, request: gr.Request):
|
| 1899 |
if request:
|
| 1900 |
print("Request headers dictionary:", request.headers)
|
|
|
|
| 1927 |
|
| 1928 |
return admin, reading_passage_admin, summary_admin, see_detail, password_text, youtube_link
|
| 1929 |
|
| 1930 |
+
def update_state(content_subject, content_grade, trascript):
|
| 1931 |
+
# inputs=[content_subject, content_grade, df_string_output],
|
| 1932 |
+
# outputs=[content_subject_state, content_grade_state, trascript_state]
|
| 1933 |
+
content_subject_state = content_subject
|
| 1934 |
+
content_grade_state = content_grade
|
| 1935 |
+
trascript_state = trascript
|
| 1936 |
+
streaming_chat_thread_id_state = create_thread_id()
|
| 1937 |
+
|
| 1938 |
+
return content_subject_state, content_grade_state, trascript_state, streaming_chat_thread_id_state
|
| 1939 |
+
|
| 1940 |
HEAD = """
|
| 1941 |
<meta charset="UTF-8">
|
| 1942 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
| 2001 |
</script>
|
| 2002 |
"""
|
| 2003 |
|
| 2004 |
+
def update_key_moments_section(key_moments):
|
| 2005 |
+
blocks = []
|
| 2006 |
+
for moment in key_moments:
|
| 2007 |
+
with gr.Row() as row:
|
| 2008 |
+
with gr.Column(scale=1):
|
| 2009 |
+
gr.Gallery(images=moment['images'], show_label=False)
|
| 2010 |
+
with gr.Column(scale=2):
|
| 2011 |
+
gr.Textbox(value=moment['text'], show_label=False, interactive=False)
|
| 2012 |
+
blocks.append(row)
|
| 2013 |
+
return blocks
|
| 2014 |
+
|
| 2015 |
with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, secondary_hue=gr.themes.colors.amber, text_size = gr.themes.sizes.text_lg), head=HEAD) as demo:
|
| 2016 |
with gr.Row() as admin:
|
| 2017 |
password = gr.Textbox(label="Password", type="password", elem_id="password_input", visible=True)
|
|
|
|
| 2021 |
web_link = gr.Textbox(label="Enter Web Page Link", visible=False)
|
| 2022 |
user_data = gr.Textbox(label="User Data", elem_id="user_data_input", visible=True)
|
| 2023 |
youtube_link_btn = gr.Button("Submit_YouTube_Link", elem_id="youtube_link_btn", visible=True)
|
| 2024 |
+
with gr.Row() as data_state:
|
| 2025 |
+
content_subject_state = gr.State() # 使用 gr.State 存储 content_subject
|
| 2026 |
+
content_grade_state = gr.State() # 使用 gr.State 存储 content_grade
|
| 2027 |
+
trascript_state = gr.State() # 使用 gr.State 存储 trascript
|
| 2028 |
+
streaming_chat_thread_id_state = gr.State() # 使用 gr.State 存储 streaming_chat_thread_id
|
| 2029 |
with gr.Tab("AI小精靈"):
|
| 2030 |
with gr.Row():
|
| 2031 |
+
with gr.Tab("飛特精靈"):
|
| 2032 |
bot_avatar = "https://junyi-avatar.s3.ap-northeast-1.amazonaws.com/live/%20%20foxcat-star-18.png?v=20231113095823614"
|
| 2033 |
user_avatar = "https://junyitopicimg.s3.amazonaws.com/s4byy--icon.jpe?v=20200513013523726"
|
| 2034 |
latex_delimiters = [{"left": "$", "right": "$", "display": False}]
|
|
|
|
| 2066 |
ai_chatbot = gr.Chatbot(avatar_images=[bot_avatar, user_avatar], label="ai_chatbot", show_share_button=False, likeable=True, show_label=False)
|
| 2067 |
ai_msg = gr.Textbox(label="Message")
|
| 2068 |
ai_send_button = gr.Button("Send", variant="primary")
|
| 2069 |
+
with gr.Tab("飛特音速版"):
|
| 2070 |
+
additional_inputs = [password, streaming_chat_thread_id_state, trascript_state, content_subject_state, content_grade_state]
|
| 2071 |
+
streaming_chat = gr.ChatInterface(
|
| 2072 |
+
fn=streaming_chat_with_open_ai,
|
| 2073 |
+
additional_inputs=additional_inputs,
|
| 2074 |
+
submit_btn="送出",
|
| 2075 |
+
retry_btn="🔄 重新回答",
|
| 2076 |
+
undo_btn="⏪ 上一步",
|
| 2077 |
+
clear_btn="🗑️ 清除全部",
|
| 2078 |
+
stop_btn="🛑 停止",
|
| 2079 |
+
)
|
| 2080 |
with gr.Tab("文章模式"):
|
| 2081 |
with gr.Row() as reading_passage_admin:
|
| 2082 |
reading_passage_kind = gr.Textbox(value="reading_passage", show_label=False)
|
|
|
|
| 2100 |
with gr.Tab("關鍵時刻"):
|
| 2101 |
with gr.Row():
|
| 2102 |
key_moments_html = gr.HTML(value="")
|
| 2103 |
+
with gr.Tab("Mind Map"):
|
| 2104 |
+
key_moments_state = gr.State()
|
| 2105 |
+
update_key_moments_button = gr.Button("更新關鍵時刻")
|
| 2106 |
+
key_moments_section = gr.Blocks()
|
| 2107 |
+
|
| 2108 |
+
update_key_moments_button.click(
|
| 2109 |
+
fn=update_key_moments_section,
|
| 2110 |
+
inputs=[],
|
| 2111 |
+
outputs=key_moments_section
|
| 2112 |
+
)
|
| 2113 |
+
|
| 2114 |
with gr.Tab("教學備課"):
|
| 2115 |
with gr.Row():
|
| 2116 |
content_subject = gr.Dropdown(label="選擇主題", choices=["數學", "自然", "國文", "英文", "社會","物理", "化學", "生物", "地理", "歷史", "公民"], value="", visible=False)
|
|
|
|
| 2285 |
file_upload.change(process_file, inputs=file_upload, outputs=[btn_1, btn_2, btn_3, df_summarise, df_string_output])
|
| 2286 |
|
| 2287 |
# 当输入 YouTube 链接时触发
|
| 2288 |
+
process_youtube_link_inputs = [password, youtube_link]
|
| 2289 |
+
process_youtube_link_outputs = [
|
| 2290 |
video_id,
|
| 2291 |
btn_1,
|
| 2292 |
btn_2,
|
|
|
|
| 2294 |
df_string_output,
|
| 2295 |
df_summarise,
|
| 2296 |
key_moments_html,
|
| 2297 |
+
key_moments_state,
|
| 2298 |
mind_map,
|
| 2299 |
mind_map_html,
|
| 2300 |
transcript_html,
|
|
|
|
| 2305 |
content_subject,
|
| 2306 |
content_grade,
|
| 2307 |
]
|
| 2308 |
+
update_state_inputs = [content_subject, content_grade, df_string_output]
|
| 2309 |
+
update_state_outputs = [content_subject_state, content_grade_state, trascript_state, streaming_chat_thread_id_state]
|
| 2310 |
+
|
| 2311 |
youtube_link.change(
|
| 2312 |
process_youtube_link,
|
| 2313 |
+
inputs=process_youtube_link_inputs,
|
| 2314 |
+
outputs=process_youtube_link_outputs
|
| 2315 |
+
).then(
|
| 2316 |
+
update_state,
|
| 2317 |
+
inputs=update_state_inputs,
|
| 2318 |
+
outputs=update_state_outputs
|
| 2319 |
)
|
| 2320 |
+
|
| 2321 |
youtube_link_btn.click(
|
| 2322 |
process_youtube_link,
|
| 2323 |
+
inputs=process_youtube_link_inputs,
|
| 2324 |
+
outputs=process_youtube_link_outputs
|
| 2325 |
+
).then(
|
| 2326 |
+
update_state,
|
| 2327 |
+
inputs=update_state_inputs,
|
| 2328 |
+
outputs=update_state_outputs
|
| 2329 |
)
|
| 2330 |
+
|
| 2331 |
|
| 2332 |
# 当输入网页链接时触发
|
| 2333 |
# web_link.change(process_web_link, inputs=web_link, outputs=[btn_1, btn_2, btn_3, df_summarise, df_string_output])
|
requirements.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
gradio
|
| 2 |
pandas
|
| 3 |
-
openai>=1.
|
| 4 |
requests
|
| 5 |
beautifulsoup4
|
| 6 |
python-docx
|
|
|
|
| 1 |
+
gradio==4.8.0
|
| 2 |
pandas
|
| 3 |
+
openai>=1.16.2
|
| 4 |
requests
|
| 5 |
beautifulsoup4
|
| 6 |
python-docx
|