|
|
|
|
|
from typing import Container |
|
from config.config import PASSWORD |
|
import gradio as gr |
|
import os |
|
import shutil |
|
import tempfile |
|
from google import genai |
|
from google.genai import types |
|
|
|
from initializer import initialize_clients, initialize_password |
|
|
|
|
|
GCS_SERVICE, GENAI_CLIENT = initialize_clients() |
|
GCS_CLIENT = GCS_SERVICE.client |
|
|
|
PASSWORD = initialize_password() |
|
|
|
def toggle_visibility(toggle_value): |
|
return gr.update(visible=toggle_value) |
|
|
|
def mock_question_answer(question, history): |
|
|
|
answers = { |
|
"文件的核心觀點是什麼?": "這份文件的核心觀點是關於人工智慧如何提升工作效率。", |
|
"有哪些關鍵詞或數據?": "關鍵詞包括:人工智慧、工作效率、數據分析。", |
|
"文件的摘要是什麼?": "這份文件討論了如何利用人工智慧工具,提升企業的運營效率和決策速度。" |
|
} |
|
response = answers.get(question, "抱歉,我無法回答這個問題。請嘗試其他問題!") |
|
history.append({"role": "user", "content": question}) |
|
history.append({"role": "assistant", "content": response}) |
|
return history, "" |
|
|
|
def mock_summary(): |
|
|
|
return "這份文件主要討論人工智慧在工作效率提升方面的應用,並提供了實際案例來說明其價值。" |
|
|
|
def add_to_file_list(file, file_list): |
|
if file: |
|
temp_dir = tempfile.gettempdir() |
|
temp_path = os.path.join(temp_dir, os.path.basename(file.name)) |
|
shutil.copy(file.name, temp_path) |
|
file_list.append(temp_path) |
|
display_list = [os.path.basename(path) if os.path.basename(path) else path for path in file_list] |
|
return gr.update(choices=display_list), None |
|
|
|
def add_youtube_to_list(youtube_link, file_list): |
|
if youtube_link: |
|
file_list.append(youtube_link) |
|
display_list = [os.path.basename(path) if os.path.basename(path) else path for path in file_list] |
|
return gr.update(choices=display_list), "" |
|
|
|
def generate_transcript(youtube_link): |
|
print(f"\n開始生成 YouTube 逐字稿: {youtube_link}") |
|
try: |
|
print("初始化 Gemini 模型設定...") |
|
video = types.Part.from_uri( |
|
file_uri=youtube_link, |
|
mime_type="video/*", |
|
) |
|
|
|
model = "gemini-2.0-flash-exp" |
|
contents = [ |
|
types.Content( |
|
role="user", |
|
parts=[ |
|
video, |
|
types.Part.from_text("""請給我帶時間軸的逐字稿,請統一用 zhTW語言""") |
|
] |
|
) |
|
] |
|
generate_content_config = types.GenerateContentConfig( |
|
temperature=1, |
|
top_p=0.95, |
|
max_output_tokens=8192, |
|
response_modalities=["TEXT"], |
|
safety_settings=[ |
|
types.SafetySetting(category="HARM_CATEGORY_HATE_SPEECH", threshold="OFF"), |
|
types.SafetySetting(category="HARM_CATEGORY_DANGEROUS_CONTENT", threshold="OFF"), |
|
types.SafetySetting(category="HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold="OFF"), |
|
types.SafetySetting(category="HARM_CATEGORY_HARASSMENT", threshold="OFF") |
|
], |
|
) |
|
|
|
print("開始串流生成逐字稿...") |
|
transcript_text = "" |
|
for chunk in GENAI_CLIENT.models.generate_content_stream( |
|
model=model, |
|
contents=contents, |
|
config=generate_content_config, |
|
): |
|
|
|
if hasattr(chunk, 'candidates') and chunk.candidates: |
|
for candidate in chunk.candidates: |
|
if (hasattr(candidate, 'content') and |
|
hasattr(candidate.content, 'parts')): |
|
for part in candidate.content.parts: |
|
if hasattr(part, 'text') and part.text: |
|
transcript_text += part.text |
|
print(".", end="", flush=True) |
|
|
|
print("\n逐字稿生成完成!") |
|
return transcript_text |
|
except Exception as e: |
|
print(f"\n生成逐字稿時發生錯誤: {str(e)}") |
|
raise |
|
|
|
def generate_summary(transcript): |
|
"""Generate a summary from the transcript using Gemini.""" |
|
try: |
|
print("\n開始生成摘要...") |
|
model = "gemini-2.0-flash-exp" |
|
contents = [ |
|
types.Content( |
|
role="user", |
|
parts=[ |
|
types.Part.from_text( |
|
f"""請根據以下逐字稿生成重點摘要,以條列方式呈現主要觀點: |
|
|
|
{transcript} |
|
|
|
請以下列格式輸出: |
|
# 主要觀點: |
|
1. [重點1] |
|
2. [重點2] |
|
... |
|
|
|
# 結論: |
|
[整體結論] |
|
""" |
|
) |
|
] |
|
) |
|
] |
|
|
|
response = GENAI_CLIENT.models.generate_content( |
|
model=model, |
|
contents=contents, |
|
) |
|
|
|
print("摘要生成完成!") |
|
return response.text |
|
except Exception as e: |
|
print(f"\n生成摘要時發生錯誤: {str(e)}") |
|
raise |
|
|
|
def process_all_files(file_list): |
|
print("\n=== 開始處理檔案 ===") |
|
print(f"待處理檔案數量: {len(file_list)}") |
|
|
|
result_text = "" |
|
transcript_text = "" |
|
|
|
for index, file in enumerate(file_list, 1): |
|
print(f"\n處理第 {index}/{len(file_list)} 個檔案: {file}") |
|
|
|
if "youtube.com" in file or "youtu.be" in file: |
|
print(f"檢測到 YouTube 連結,開始生成逐字稿...") |
|
try: |
|
transcript = generate_transcript(file) |
|
print("✓ YouTube 逐字稿生成成功") |
|
result_text += f"🟢 YouTube 影片處理完成: {file}\n" |
|
transcript_text += f"\n=== {file} 的逐字稿 ===\n{transcript}\n" |
|
except Exception as e: |
|
print(f"✗ YouTube 逐字稿生成失敗: {str(e)}") |
|
result_text += f"🔴 YouTube 影片處理失敗: {file}\n" |
|
else: |
|
print(f"處理一般檔案: {file}") |
|
try: |
|
|
|
print("✓ 檔案處理成功") |
|
result_text += f"🟢 檔案處理完成: {file}\n" |
|
except Exception as e: |
|
print(f"✗ 檔案處理失敗: {str(e)}") |
|
result_text += f"🔴 檔案處理失敗: {file}\n" |
|
|
|
print("\n=== 檔案處理完成 ===") |
|
return result_text, transcript_text |
|
|
|
def process_with_auth(password, file_list): |
|
"""包含密碼驗證的處理函數""" |
|
if not password or password != PASSWORD: |
|
return "請輸入正確的密碼", "", gr.update(visible=False) |
|
|
|
result_text, transcript_text = process_all_files(file_list) |
|
return result_text, transcript_text |
|
|
|
def on_summary_click(transcript): |
|
if not transcript: |
|
return "請先上傳文件或輸入 YouTube 連結並處理完成後再生成摘要。" |
|
|
|
summary = generate_summary(transcript) |
|
return summary |
|
|
|
with gr.Blocks() as demo: |
|
|
|
with gr.Row(): |
|
gr.Markdown("# AI Notes Assistant") |
|
password_input = gr.Textbox(label="password") |
|
|
|
with gr.Row(): |
|
source_toggle = gr.Checkbox(label="顯示來源選單", value=True) |
|
chat_toggle = gr.Checkbox(label="顯示對話區域", value=True) |
|
feature_toggle = gr.Checkbox(label="顯示功能卡片", value=True) |
|
|
|
with gr.Row(): |
|
with gr.Column(visible=True) as source_column: |
|
gr.Markdown("### 來源選單") |
|
|
|
file_list = gr.State([]) |
|
|
|
with gr.Tab("YouTube 連結"): |
|
youtube_link = gr.Textbox(label="輸入 YouTube 連結") |
|
add_youtube_button = gr.Button("添加到來源列表") |
|
add_youtube_button.click(add_youtube_to_list, inputs=[youtube_link, file_list], outputs=[file_list, youtube_link]) |
|
|
|
with gr.Tab("上傳檔案"): |
|
upload_file = gr.File(label="從電腦添加文件", file_types=[".txt", ".pdf", ".docx"]) |
|
add_file_button = gr.Button("添加到來源列表") |
|
add_file_button.click(add_to_file_list, inputs=[upload_file, file_list], outputs=[file_list, upload_file]) |
|
|
|
file_display = gr.CheckboxGroup(label="已上傳的文件", interactive=True) |
|
|
|
process_files_button = gr.Button("處理檔案") |
|
rag_result = gr.Textbox(label="處理狀態", interactive=False) |
|
|
|
file_list.change(lambda x: gr.update(choices = [os.path.basename(path) if os.path.basename(path) else path for path in x]), inputs=file_list, outputs=file_display) |
|
|
|
with gr.Column(visible=True) as chat_column: |
|
gr.Markdown("### 對話區域") |
|
chatbot = gr.Chatbot(label="聊天記錄", type="messages") |
|
question = gr.Textbox(label="輸入問題,例如:文件的核心觀點是什麼?") |
|
ask_button = gr.Button("提問") |
|
|
|
with gr.Column(visible=True) as feature_column: |
|
gr.Markdown("### 功能卡片") |
|
with gr.Tab("摘要生成"): |
|
summary_button = gr.Button("生成摘要", visible=False) |
|
summary_output = gr.Markdown( |
|
label="摘要", |
|
show_label=True, |
|
show_copy_button=True, |
|
container=True |
|
) |
|
with gr.Tab("逐字稿"): |
|
transcript_display = gr.Textbox( |
|
label="YouTube 逐字稿", |
|
interactive=False, |
|
lines=10, |
|
show_copy_button=True, |
|
placeholder="處理 YouTube 影片後,逐字稿將顯示在這裡..." |
|
) |
|
with gr.Tab("其他功能"): |
|
gr.Markdown("此處可以添加更多功能卡片") |
|
|
|
source_toggle.change(toggle_visibility, inputs=source_toggle, outputs=source_column) |
|
chat_toggle.change(toggle_visibility, inputs=chat_toggle, outputs=chat_column) |
|
feature_toggle.change(toggle_visibility, inputs=feature_toggle, outputs=feature_column) |
|
|
|
|
|
process_files_button.click( |
|
fn=process_with_auth, |
|
inputs=[password_input, file_list], |
|
outputs=[ |
|
rag_result, |
|
transcript_display |
|
] |
|
).then( |
|
fn=on_summary_click, |
|
inputs=[transcript_display], |
|
outputs=[summary_output] |
|
) |
|
|
|
history = gr.State([]) |
|
ask_button.click(mock_question_answer, inputs=[question, history], outputs=[chatbot, question]) |
|
summary_button.click( |
|
fn=on_summary_click, |
|
inputs=[transcript_display], |
|
outputs=[summary_output] |
|
) |
|
|
|
|
|
|
|
demo.launch(share=True) |