diff --git a/.gitattributes b/.gitattributes index b041bcafc40688039894f941c32e65ad8d72e10f..a6344aac8c09253b3b630fb776ae94478aa0275b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,37 +1,35 @@ -*.7z filter=lfs diff=lfs merge=lfs -text -*.arrow filter=lfs diff=lfs merge=lfs -text -*.bin filter=lfs diff=lfs merge=lfs -text -*.bz2 filter=lfs diff=lfs merge=lfs -text -*.ckpt filter=lfs diff=lfs merge=lfs -text -*.ftz filter=lfs diff=lfs merge=lfs -text -*.gz filter=lfs diff=lfs merge=lfs -text -*.h5 filter=lfs diff=lfs merge=lfs -text -*.joblib filter=lfs diff=lfs merge=lfs -text -*.lfs.* filter=lfs diff=lfs merge=lfs -text -*.mlmodel filter=lfs diff=lfs merge=lfs -text -*.model filter=lfs diff=lfs merge=lfs -text -*.msgpack filter=lfs diff=lfs merge=lfs -text -*.npy filter=lfs diff=lfs merge=lfs -text -*.npz filter=lfs diff=lfs merge=lfs -text -*.onnx filter=lfs diff=lfs merge=lfs -text -*.ot filter=lfs diff=lfs merge=lfs -text -*.parquet filter=lfs diff=lfs merge=lfs -text -*.pb filter=lfs diff=lfs merge=lfs -text -*.pickle filter=lfs diff=lfs merge=lfs -text -*.pkl filter=lfs diff=lfs merge=lfs -text -*.pt filter=lfs diff=lfs merge=lfs -text -*.pth filter=lfs diff=lfs merge=lfs -text -*.rar filter=lfs diff=lfs merge=lfs -text -*.safetensors filter=lfs diff=lfs merge=lfs -text -saved_model/**/* filter=lfs diff=lfs merge=lfs -text -*.tar.* filter=lfs diff=lfs merge=lfs -text -*.tar filter=lfs diff=lfs merge=lfs -text -*.tflite filter=lfs diff=lfs merge=lfs -text -*.tgz filter=lfs diff=lfs merge=lfs -text -*.wasm filter=lfs diff=lfs merge=lfs -text -*.xz filter=lfs diff=lfs merge=lfs -text -*.zip filter=lfs diff=lfs merge=lfs -text -*.zst filter=lfs diff=lfs merge=lfs -text -*tfevents* filter=lfs diff=lfs merge=lfs -text -segment_0[[:space:]](2).wav filter=lfs diff=lfs merge=lfs -text -sample.wav filter=lfs diff=lfs merge=lfs -text +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore deleted file mode 100644 index bf28fad48d89996ff75bb93d5dfbcdb571e6267b..0000000000000000000000000000000000000000 Binary files a/.gitignore and /dev/null differ diff --git a/Dockerfile b/Dockerfile index ba0cbf0bbfdaa2dcd279b7375d18ea37231c6e4f..a7428f122df32bf9fc270501e752475f448afecb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,17 @@ -FROM nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04 - -# タイムゾーン設定 -RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime - -# Python3、pip、ffmpegをインストール -RUN apt-get update && \ - apt-get install -y python3 python3-pip ffmpeg && \ - rm -rf /var/lib/apt/lists/* - -# pipを最新版にアップグレード -RUN python3 -m pip install --upgrade pip - -WORKDIR /app - -# requirements.txt をコンテナ内にコピーして、必要なパッケージをインストール -COPY requirements.txt /app/ - -RUN python3 -m pip install --no-cache-dir -r requirements.txt - -COPY . . - -CMD ["python3", "app.py"] \ No newline at end of file +# Dockerfile +FROM python:3.9-slim + +WORKDIR /app + +# 依存関係のインストール +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# アプリの全ファイルをコピー +COPY . . +RUN touch weak_phrases.json && chmod 666 weak_phrases.json + +# Hugging Face Spaces ではポート 7860 を使用する +EXPOSE 7860 + +CMD ["python", "app.py"] diff --git a/README.md b/README.md index 5086688e050012af584d3809ae49f194c48bc3eb..a0df14e3bd6107dc433bcaec0d9544ddc193101b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ ---- -title: JusTalk -emoji: ⚡ -colorFrom: gray -colorTo: blue -sdk: docker -pinned: false ---- - -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +--- +title: JusTalk +emoji: ⚡ +colorFrom: gray +colorTo: blue +sdk: docker +pinned: false +--- + +Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference diff --git a/__pycache__/analyze.cpython-310.pyc b/__pycache__/analyze.cpython-310.pyc deleted file mode 100644 index 57e0787600dbdcd4accae13b047c9a871b2600f1..0000000000000000000000000000000000000000 Binary files a/__pycache__/analyze.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/app.cpython-311.pyc b/__pycache__/app.cpython-311.pyc deleted file mode 100644 index 9cae6e87f4a7b488357eed10954c8553edff2221..0000000000000000000000000000000000000000 Binary files a/__pycache__/app.cpython-311.pyc and /dev/null differ diff --git a/__pycache__/database.cpython-310.pyc b/__pycache__/database.cpython-310.pyc deleted file mode 100644 index 0972fe47eee514a6ec9078f76f9f90b0338eb33b..0000000000000000000000000000000000000000 Binary files a/__pycache__/database.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/database.cpython-311.pyc b/__pycache__/database.cpython-311.pyc deleted file mode 100644 index 1dca8c2800e97e7f7d8869bf57f39110e749e278..0000000000000000000000000000000000000000 Binary files a/__pycache__/database.cpython-311.pyc and /dev/null differ diff --git a/__pycache__/login.cpython-310.pyc b/__pycache__/login.cpython-310.pyc deleted file mode 100644 index 0ebf0713d6f605166aeba8011d96252b8190bc31..0000000000000000000000000000000000000000 Binary files a/__pycache__/login.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/models.cpython-310.pyc b/__pycache__/models.cpython-310.pyc deleted file mode 100644 index ea3111a26f61ccfc815b418387e230883d61f721..0000000000000000000000000000000000000000 Binary files a/__pycache__/models.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/new_record.cpython-310.pyc b/__pycache__/new_record.cpython-310.pyc deleted file mode 100644 index 1366e177b783a1b75e970e01002819bde498bb28..0000000000000000000000000000000000000000 Binary files a/__pycache__/new_record.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/process.cpython-310.pyc b/__pycache__/process.cpython-310.pyc deleted file mode 100644 index 45ab864bb90e5589b00ecd46182bd1228fd11f18..0000000000000000000000000000000000000000 Binary files a/__pycache__/process.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/transcription.cpython-310.pyc b/__pycache__/transcription.cpython-310.pyc deleted file mode 100644 index a395eb2d6259f2cb34a37d6feaca1320ced5fdf5..0000000000000000000000000000000000000000 Binary files a/__pycache__/transcription.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/users.cpython-310.pyc b/__pycache__/users.cpython-310.pyc deleted file mode 100644 index 790f44463cffdd5163dc530ae4dbdd766e63ecc5..0000000000000000000000000000000000000000 Binary files a/__pycache__/users.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/users.cpython-311.pyc b/__pycache__/users.cpython-311.pyc deleted file mode 100644 index a29bfdf59bb9143d162b4b0f486ed99b85bfff2e..0000000000000000000000000000000000000000 Binary files a/__pycache__/users.cpython-311.pyc and /dev/null differ diff --git a/analyze.py b/analyze.py deleted file mode 100644 index 4bb8c8f0a6e834b52b5e665f6181e894ffc0921b..0000000000000000000000000000000000000000 --- a/analyze.py +++ /dev/null @@ -1,224 +0,0 @@ -import json -import requests -import os - -class TextAnalyzer: - """ - テキストのハラスメント検出と会話評価を行うクラス。 - """ - def __init__(self, file_path, keywords): - """ - TextAnalyzer クラスのコンストラクタ。 - - Args: - file_path (str): 分析するテキストファイルのパス。 - keywords (list): ハラスメント検出に使用するキーワードのリスト。 - """ - self.file_path = file_path - self.keywords = keywords - self.text_content = None # テキストファイルの内容を格納 - self.harassment_detected = False # ハラスメントが検出されたかどうか - self.harassment_keywords = [] # 検出されたハラスメントキーワードのリスト - self.deepseek_analysis = {} # DeepSeek API による分析結果を格納する辞書 - self.api_key = None - - def load_text(self): - """ - テキストファイルを読み込み、その内容を self.text_content に格納する。 - - Returns: - bool: ファイルの読み込みに成功した場合は True、失敗した場合は False。 - """ - try: - with open(self.file_path, 'r', encoding='utf-8') as file: - self.text_content = file.read() - return True - except Exception as e: - print(f"ファイル読み込みエラー: {e}") - return False - - def detect_harassment(self): - """ - テキスト内容からハラスメントを検出する。 - - Returns: - bool: ハラスメントが検出された場合は True、それ以外は False。 - """ - if not self.text_content: - return False - - self.harassment_keywords = [] - for keyword in self.keywords: - if keyword in self.text_content: - self.harassment_detected = True - self.harassment_keywords.append(keyword) - - return self.harassment_detected - - def analyze_with_deepseek(self, api_key=None, api_url="https://api.deepseek.com/v1/chat/completions"): - """ - DeepSeek API を使用して会話を分析する。会話レベルやハラスメントの詳細な検出を行う。 - - Args: - api_key (str, optional): DeepSeek API キー。指定されない場合は環境変数から取得。 - api_url (str, optional): DeepSeek API の URL。デフォルトは標準のチャット補完エンドポイント。 - - Returns: - bool: 分析に成功した場合は True、失敗した場合は False。 - """ - if not self.text_content: - return False - - # 提供された API キーを使用するか、環境変数から取得する - if api_key: - self.api_key = api_key - else: - self.api_key = os.environ.get("DEEPSEEK_API_KEY") - if not self.api_key: - print("DeepSeek API キーが提供されておらず、環境変数にも見つかりませんでした。") - return False - - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self.api_key}" - } - - prompt = f""" - 以下の会話を分析し、結果を JSON 形式で返してください。1 から 10 のスケールで評価し、10 が最高です。 - 厳密に評価してください。ハラスメントが存在する場合は、その種類を具体的に記述してください。 - 評価基準: - 1. conversationLevel: 会話のレベル (初心者、中級者、上級者)。 - 2. harassmentPresent: 会話にハラスメント表現が含まれているかどうか (true/false)。 - 3. harassmentType: ハラスメントが存在する場合、その種類を具体的に記述。 - 4. topicAppropriateness: 会話のトピックが適切かどうか。 - 5. improvementSuggestions: 会話を改善するための具体的な提案。 - 6. repetition: 同じことがどの程度繰り返されているか。(1-10) - 7. pleasantConversation: 会話がどの程度心地よいか。(1-10) - 8. blameOrHarassment: 会話がどの程度相手を責めたり、ハラスメントをしているか。(1-10) - - 会話内容: - {self.text_content} - - JSON 形式のみを返してください。 - """ - - data = { - "model": "deepseek-chat", - "messages": [{"role": "user", "content": prompt}], - "response_format": {"type": "json_object"} - } - - try: - response = requests.post(api_url, headers=headers, json=data) - response.raise_for_status() - - result = response.json() - deepseek_response = json.loads(result["choices"][0]["message"]["content"]) - - # 指定されたキーを使用して、インスタンス変数に値を割り当てる - self.deepseek_analysis = { - "conversationLevel": deepseek_response.get("conversationLevel"), - "harassmentPresent": deepseek_response.get("harassmentPresent"), - "harassmentType": deepseek_response.get("harassmentType"), - "topicAppropriateness": deepseek_response.get("topicAppropriateness"), - "improvementSuggestions": deepseek_response.get("improvementSuggestions"), - "repetition": deepseek_response.get("repetition"), - "pleasantConversation": deepseek_response.get("pleasantConversation"), - "blameOrHarassment": deepseek_response.get("blameOrHarassment"), - } - - return True - except requests.exceptions.RequestException as e: - print(f"DeepSeek API リクエストエラー: {e}") - return False - except json.JSONDecodeError as e: - print(f"DeepSeek API レスポンスの JSON デコードエラー: {e}") - print(f"レスポンス内容: {response.text}") - return False - except KeyError as e: - print(f"DeepSeek API レスポンスのキーエラー: {e}") - print(f"レスポンス内容: {response.text}") - return False - except Exception as e: - print(f"DeepSeek API エラー: {e}") - return False - - def get_analysis_results(self): - """ - 分析結果を返す。 - - Returns: - dict: 分析結果を含む辞書。 - """ - results = { - "text_content": self.text_content, - "basic_harassment_detection": { - "detected": self.harassment_detected, - "matching_keywords": self.harassment_keywords - }, - "deepseek_analysis": self.deepseek_analysis - } - - return results - - def analyze(self, api_key=None): - """ - すべての分析を実行し、結果を返す。 - - Args: - api_key (str, optional): DeepSeek API キー。 - - Returns: - dict: 分析結果またはエラーメッセージを含む辞書。 - """ - if not self.load_text(): - return {"error": "テキストファイルの読み込みに失敗しました。"} - - self.detect_harassment() - - if not self.analyze_with_deepseek(api_key): - return {"error": "DeepSeek API 分析に失敗しました。"} - - return self.get_analysis_results() - -''' -# 使用例 -if __name__ == "__main__": - # ハラスメント検出用のキーワード例 - harassment_keywords = [ - "バカ", "馬鹿", "アホ", "死ね", "クソ", "うざい", - "きもい", "キモい", "ブス", "デブ", "ハゲ", - "セクハラ", "パワハラ", "モラハラ" - ] - - # 分析インスタンスの作成 - analyzer = TextAnalyzer("./2.txt", harassment_keywords) - - # DeepSeek API キー (環境変数から取得するか、直接渡す) - # api_key = os.environ.get("DEEPSEEK_API_KEY") - - - # 分析の実行 - results = analyzer.analyze(api_key=api_key) - - # 結果の出力 - print(json.dumps(results, ensure_ascii=False, indent=2)) - - # 特定の値へのアクセス例 - if "deepseek_analysis" in results and results["deepseek_analysis"]: - deepseek_data = results["deepseek_analysis"] - conversation_level = deepseek_data.get("conversationLevel") - harassment_present = deepseek_data.get("harassmentPresent") - harassment_type = deepseek_data.get("harassmentType") - repetition = deepseek_data.get("repetition") - pleasantConversation = deepseek_data.get("pleasantConversation") - blameOrHarassment = deepseek_data.get("blameOrHarassment") - - print("\n--- DeepSeek 分析結果 ---") - print(f"会話レベル: {conversation_level}") - print(f"ハラスメントの有無: {harassment_present}") - print(f"ハラスメントの種類: {harassment_type}") - print(f"繰り返しの程度: {repetition}") - print(f"会話の心地よさ: {pleasantConversation}") - print(f"非難またはハラスメントの程度: {blameOrHarassment}") -''' \ No newline at end of file diff --git a/app.py b/app.py index 17d57b3085b4899cd83c599ce26c0765cb01bef9..4f7121047d1e0141dc7dd52b98775c488c5e2715 100644 --- a/app.py +++ b/app.py @@ -1,497 +1,48 @@ -from flask import Flask, request, jsonify, render_template, send_from_directory +from flask import Flask, request, jsonify, send_from_directory import base64 -from pydub import AudioSegment import os -import shutil -import requests -import tempfile -import json -from process import AudioProcessor -from transcription import TranscriptionMaker -from analyze import TextAnalyzer -from flask_cors import CORS -process = AudioProcessor() -transcripter = TranscriptionMaker() -app = Flask(__name__) - -# CORS設定: すべてのオリジンからのリクエストを許可 -# 必要であれば、特定のオリジンやメソッド、ヘッダーをより厳密に指定できます -# 例: CORS(app, resources={r"/api/*": {"origins": "http://localhost:3000"}}, supports_credentials=True) -CORS(app, origins="*", methods=["GET", "POST", "DELETE", "OPTIONS"], headers=["Content-Type", "Authorization"]) - -# GASのエンドポイントURL -GAS_URL = "https://script.google.com/macros/s/AKfycbwR2cnMKVU1AxoT9NDaeZaNUaTiwRX64Ul0sH0AU4ccP49Byph-TpxtM_Lwm4G9zLnuYA/exec" - -users = [] # 選択されたユーザーのリスト -all_users = [] # 利用可能なすべてのユーザーのリスト -transcription_text = "" -harassment_keywords = [ - "バカ", "馬鹿", "アホ", "死ね", "クソ", "うざい", - "きもい", "キモい", "ブス", "デブ", "ハゲ", - "セクハラ", "パワハラ", "モラハラ" -] -total_audio = "" +app = Flask(__name__) -@app.route('/index', methods=['GET', 'POST']) +@app.route('/') def index(): - return render_template('index.html', users=users) + return send_from_directory(".", "index.html") -# フィードバック画面(テンプレート: feedback.html) -@app.route('/feedback', methods=['GET', 'POST']) +@app.route('/feedback',methods=['POST']) def feedback(): - return render_template('feedback.html') - -# 会話詳細画面(テンプレート: talkDetail.html) -@app.route('/talk_detail', methods=['GET', 'POST']) -def talk_detail(): - return render_template('talkDetail.html') - -# 音声登録画面(テンプレート: userRegister.html) -@app.route('/userregister', methods=['GET', 'POST']) -def userregister(): - return render_template('userRegister.html') - -# 人数確認 -@app.route('/confirm', methods=['GET']) -def confirm(): - global all_users - # 最新のユーザーリストを取得 - try: - update_all_users() - except Exception as e: - print(f"ユーザーリストの更新エラー: {str(e)}") - return jsonify({'members': users, 'all_members': all_users}), 200 - -# リセット画面(テンプレート: reset.html) -@app.route('/reset_html', methods=['GET', 'POST']) -def reset_html(): - return render_template('reset.html') - -# メンバー削除&累積音声削除 -@app.route('/reset_member', methods=['GET', 'POST']) -def reset_member(): - global users - global total_audio - global transcription_text - - # 一時ディレクトリのクリーンアップ - if total_audio: - process.delete_files_in_directory(total_audio) - process.delete_files_in_directory('/tmp/data/transcription_audio') - - # 書き起こしテキストの削除 - if os.path.exists(transcription_text): - try: - os.remove(transcription_text) - print(f"{transcription_text} を削除しました。") - except Exception as e: - print(f"ファイル削除中にエラーが発生しました: {e}") - - transcription_text = "" - - try: - data = request.get_json() - if not data or "names" not in data: - return jsonify({"status": "error", "message": "Invalid request body"}), 400 - - names = data.get("names", []) - - # GASからファイルを削除 - for name in names: - try: - delete_from_cloud(f"{name}.wav") - print(f"クラウドから {name}.wav を削除しました。") - except Exception as e: - print(f"クラウド削除中にエラーが発生しました: {e}") - return jsonify({"status": "error", "message": f"Failed to delete {name} from cloud: {e}"}), 500 - - # usersリストから削除するユーザーを除外 - users = [u for u in users if u not in names] - - # 全ユーザーリストの更新 - update_all_users() - - return jsonify({"status": "success", "message": "Members deleted successfully", "users": users}), 200 - - except Exception as e: - print(f"An unexpected error occurred: {e}") - return jsonify({"status": "error", "message": f"Internal server error: {e}"}), 500 - -# 書き起こし作成エンドポイント -@app.route('/transcription', methods=['GET', 'POST']) -def transcription(): - global transcription_text - global total_audio - - if not os.path.exists(transcription_text) or not transcription_text: - try: - if not total_audio or not os.path.exists(total_audio): - return jsonify({"error": "No audio segments provided"}), 400 - transcription_text = transcripter.create_transcription(total_audio) - print("transcription") - print(transcription_text) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - try: - with open(transcription_text, 'r', encoding='utf-8') as file: - file_content = file.read() - print(file_content) - return jsonify({'transcription': file_content}), 200 - except FileNotFoundError: - return jsonify({"error": "Transcription file not found"}), 404 - except Exception as e: - return jsonify({"error": f"Unexpected error: {str(e)}"}), 500 - -# AI分析エンドポイント -@app.route('/analyze', methods=['GET', 'POST']) -def analyze(): - global transcription_text - global total_audio - - if not os.path.exists(transcription_text) or not transcription_text: - try: - if not total_audio: - return jsonify({"error": "No audio segments provided"}), 400 - transcription_text = transcripter.create_transcription(total_audio) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - analyzer = TextAnalyzer(transcription_text, harassment_keywords) - api_key = os.environ.get("DEEPSEEK") - if api_key is None: - raise ValueError("DEEPSEEK_API_KEY が設定されていません。") - - results = analyzer.analyze(api_key=api_key) - - print(json.dumps(results, ensure_ascii=False, indent=2)) - - if "deepseek_analysis" in results and results["deepseek_analysis"]: - deepseek_data = results["deepseek_analysis"] - conversation_level = deepseek_data.get("conversationLevel") - harassment_present = deepseek_data.get("harassmentPresent") - harassment_type = deepseek_data.get("harassmentType") - repetition = deepseek_data.get("repetition") - pleasantConversation = deepseek_data.get("pleasantConversation") - blameOrHarassment = deepseek_data.get("blameOrHarassment") - - print("\n--- DeepSeek 分析結果 ---") - print(f"会話レベル: {conversation_level}") - print(f"ハラスメントの有無: {harassment_present}") - print(f"ハラスメントの種類: {harassment_type}") - print(f"繰り返しの程度: {repetition}") - print(f"会話の心地よさ: {pleasantConversation}") - print(f"非難またはハラスメントの程度: {blameOrHarassment}") - - return jsonify({"results": results}), 200 - - -# クラウドから音声を取得してローカルに保存する関数 -def download_from_cloud(filename, local_path): - try: - payload = { - "action": "download", - "fileName": filename - } - - print(f"クラウドから {filename} をダウンロード中...") - response = requests.post(GAS_URL, json=payload) - if response.status_code != 200: - print(f"ダウンロードエラー: ステータスコード {response.status_code}") - print(f"レスポンス: {response.text}") - raise Exception(f"クラウドからのダウンロードに失敗しました: {response.text}") - - try: - res_json = response.json() - except: - print("JSONデコードエラー、レスポンス内容:") - print(response.text[:500]) # 最初の500文字だけ表示 - raise Exception("サーバーからの応答をJSONとして解析できませんでした") - - if res_json.get("status") != "success": - print(f"ダウンロードステータスエラー: {res_json.get('message')}") - raise Exception(f"クラウドからのダウンロードに失敗しました: {res_json.get('message')}") - - # Base64文字列をデコード - base64_data = res_json.get("base64Data") - if not base64_data: - print("Base64データが存在しません") - raise Exception("応答にBase64データが含まれていません") - - try: - audio_binary = base64.b64decode(base64_data) - except Exception as e: - print(f"Base64デコードエラー: {str(e)}") - raise Exception(f"音声データのデコードに失敗しました: {str(e)}") - - # 指定パスに保存 - os.makedirs(os.path.dirname(local_path), exist_ok=True) - with open(local_path, 'wb') as f: - f.write(audio_binary) - - print(f"{filename} をローカルに保存しました: {local_path}") - - # データの整合性チェック(ファイルサイズが0より大きいかなど) - if os.path.getsize(local_path) <= 0: - raise Exception(f"保存されたファイル {local_path} のサイズが0バイトです") - - return local_path - except Exception as e: - print(f"ダウンロード中にエラーが発生しました: {str(e)}") - # エラーを上位に伝播させる - raise - -# クラウドからファイルを削除する関数 -def delete_from_cloud(filename): - payload = { - "action": "delete", - "fileName": filename - } - response = requests.post(GAS_URL, json=payload) - if response.status_code != 200: - raise Exception(f"クラウドからの削除に失敗しました: {response.text}") - - res_json = response.json() - if res_json.get("status") != "success": - raise Exception(f"クラウドからの削除に失敗しました: {res_json.get('message')}") - - return True -# すべてのベース音声ユーザーリストを更新する関数 -def update_all_users(): - global all_users - - payload = {"action": "list"} - response = requests.post(GAS_URL, json=payload) - if response.status_code != 200: - raise Exception(f"GAS一覧取得エラー: {response.text}") - - res_json = response.json() - if res_json.get("status") != "success": - raise Exception(f"GAS一覧取得失敗: {res_json.get('message')}") - - # ファイル名から拡張子を除去してユーザーリストを作成 - all_users = [os.path.splitext(filename)[0] for filename in res_json.get("fileNames", [])] - return all_users + return send_from_directory(".","feedback.html") -# 音声アップロード&解析エンドポイント @app.route('/upload_audio', methods=['POST']) def upload_audio(): - global total_audio - global users - try: data = request.get_json() - if not data or 'audio_data' not in data: - return jsonify({"error": "音声データがありません"}), 400 - - # リクエストからユーザーリストを取得(指定がなければ現在のusersを使用) - if 'selected_users' in data and data['selected_users']: - users = data['selected_users'] - print(f"選択されたユーザー: {users}") - - if not users: - return jsonify({"error": "選択されたユーザーがいません"}), 400 - - # Base64デコードして音声バイナリを取得 - audio_binary = base64.b64decode(data['audio_data']) - - upload_name = 'tmp' - audio_dir = "/tmp/data" - os.makedirs(audio_dir, exist_ok=True) - audio_path = os.path.join(audio_dir, f"{upload_name}.wav") - with open(audio_path, 'wb') as f: - f.write(audio_binary) - - print(f"処理を行うユーザー: {users}") - - # ベース音声を一時ディレクトリにダウンロード - temp_dir = "/tmp/data/base_audio" - os.makedirs(temp_dir, exist_ok=True) - - # 各ユーザーの参照音声ファイルのパスをリストに格納 - reference_paths = [] - for user in users: - try: - ref_path = os.path.join(temp_dir, f"{user}.wav") - if not os.path.exists(ref_path): - # クラウドから取得 - download_from_cloud(f"{user}.wav", ref_path) - print(f"クラウドから {user}.wav をダウンロードしました") - - if not os.path.exists(ref_path): - return jsonify({"error": "参照音声ファイルが見つかりません", "details": ref_path}), 500 - - reference_paths.append(ref_path) - except Exception as e: - return jsonify({"error": f"ユーザー {user} の音声取得に失敗しました", "details": str(e)}), 500 - - # 複数人の場合は参照パスのリストを、1人の場合は単一のパスを渡す - if len(users) > 1: - print("複数人の場合の処理") - matched_times, merged_segments = process.process_multi_audio(reference_paths, audio_path, users, threshold=0.05) - total_audio = transcripter.save_marged_segments(merged_segments) - # 各メンバーのrateを計算 - total_time = sum(matched_times) - rates = [(time / total_time) * 100 if total_time > 0 else 0 for time in matched_times] - - # ユーザー名と話した割合をマッピング - user_rates = {users[i]: rates[i] for i in range(len(users))} - return jsonify({"rates": rates, "user_rates": user_rates}), 200 - else: - matched_time, unmatched_time, merged_segments = process.process_audio(reference_paths[0], audio_path, users[0], threshold=0.05) - total_audio = transcripter.save_marged_segments(merged_segments) - print("単一ユーザーの処理") - total_time = matched_time + unmatched_time - rate = (matched_time / total_time) * 100 if total_time > 0 else 0 - return jsonify({"rate": rate, "user": users[0]}), 200 - - except Exception as e: - print("Error in /upload_audio:", str(e)) - return jsonify({"error": "サーバーエラー", "details": str(e)}), 500 + if not data: + return jsonify({"error": "JSONが送信されていません"}), 400 -# ユーザー選択画面(テンプレート: userSelect.html) + audio_data = data.get('audio_data') + if not audio_data: + return jsonify({"error": "音声データが送信されていません"}), 400 -# ユーザー選択画面(テンプレート: userSelect.html) -@app.route('/') -@app.route('/userselect', methods=['GET']) -def userselect(): - return render_template('userSelect.html') - -# 選択したユーザーを設定するエンドポイント -@app.route('/select_users', methods=['POST']) -def select_users(): - global users - - try: - data = request.get_json() - if not data or 'users' not in data: - return jsonify({"error": "ユーザーリストがありません"}), 400 - - users = data['users'] - print(f"選択されたユーザー: {users}") - - return jsonify({"status": "success", "selected_users": users}), 200 - except Exception as e: - print("Error in /select_users:", str(e)) - return jsonify({"error": "サーバーエラー", "details": str(e)}), 500 - -@app.route('/reset', methods=['GET']) -def reset(): - global users - users = [] - global total_audio - global transcription_text - - # 一時ディレクトリのクリーンアップ - if total_audio: - process.delete_files_in_directory(total_audio) - process.delete_files_in_directory('/tmp/data/transcription_audio') - - # 書き起こしテキストの削除 - if os.path.exists(transcription_text): + # Base64デコード try: - os.remove(transcription_text) - print(f"{transcription_text} を削除しました。") - except Exception as e: - print(f"ファイル削除中にエラーが発生しました: {e}") - - transcription_text = "" - - return jsonify({"status": "success", "message": "Users reset"}), 200 - -@app.route('/copy_selected_files', methods=['POST']) -def copy_selected_files(): - try: - data = request.get_json() - if not data or "names" not in data: - return jsonify({"error": "namesパラメータが存在しません"}), 400 - - names = data["names"] - dest_dir = "/tmp/data/selected_audio" # コピー先のフォルダ - os.makedirs(dest_dir, exist_ok=True) - - copied_files = [] - for name in names: - dest_path = os.path.join(dest_dir, f"{name}.wav") - try: - # クラウドから直接ダウンロード - download_from_cloud(f"{name}.wav", dest_path) - copied_files.append(name) - print(f"{name}.wav を {dest_path} にダウンロードしました。") - except Exception as e: - print(f"ダウンロード中にエラーが発生しました: {e}") - continue + audio_binary = base64.b64decode(audio_data) + except Exception as decode_err: + return jsonify({"error": "Base64デコードに失敗しました", "details": str(decode_err)}), 400 - return jsonify({"status": "success", "copied": copied_files}), 200 + # 書き込み用ディレクトリとして /tmp/data を使用(/tmp は書き込み可能) + persist_dir = "/tmp/data" + os.makedirs(persist_dir, exist_ok=True) - except Exception as e: - print("Error in /copy_selected_files:", str(e)) - return jsonify({"error": "サーバー内部エラー", "details": str(e)}), 500 - -@app.route('/clear_tmp', methods=['GET']) -def clear_tmp(): - try: - tmp_dir = "/tmp/data" # アプリケーションが使用しているtmpフォルダ - # ファイルのみの削除 - process.delete_files_in_directory(tmp_dir) - # フォルダがあれば再帰的に削除 - for item in os.listdir(tmp_dir): - item_path = os.path.join(tmp_dir, item) - if os.path.isdir(item_path): - shutil.rmtree(item_path) - print(f"ディレクトリを削除しました: {item_path}") + filepath = os.path.join(persist_dir, "recorded_audio.wav") + with open(filepath, 'wb') as f: + f.write(audio_binary) - return jsonify({"status": "success", "message": "tmp配下がすべて削除されました"}), 200 + return jsonify({"message": "音声が正常に保存されました", "filepath": filepath}), 200 except Exception as e: - print("Error in /clear_tmp:", str(e)) + app.logger.error("エラー: %s", str(e)) return jsonify({"error": "サーバー内部エラー", "details": str(e)}), 500 -@app.route('/upload_base_audio', methods=['POST']) -def upload_base_audio(): - global all_users - - try: - data = request.get_json() - if not data or 'audio_data' not in data or 'name' not in data: - return jsonify({"error": "音声データまたは名前がありません"}), 400 - name = data['name'] - print(f"登録名: {name}") - - # GASのアップロードエンドポイントにリクエスト - payload = { - "action": "upload", - "fileName": f"{name}.wav", - "base64Data": data['audio_data'] - } - - response = requests.post(GAS_URL, json=payload) - if response.status_code != 200: - return jsonify({"error": "GASアップロードエラー", "details": response.text}), 500 - - res_json = response.json() - if res_json.get("status") != "success": - return jsonify({"error": "GASアップロード失敗", "details": res_json.get("message")}), 500 - - # 全ユーザーリストを更新 - update_all_users() - - return jsonify({"state": "Registration Success!", "driveFileId": res_json.get("fileId")}), 200 - except Exception as e: - print("Error in /upload_base_audio:", str(e)) - return jsonify({"error": "サーバーエラー", "details": str(e)}), 500 - -@app.route('/list_base_audio', methods=['GET']) -def list_base_audio(): - try: - global all_users - all_users = update_all_users() - return jsonify({"status": "success", "id": all_users}), 200 - except Exception as e: - print("Error in /list_base_audio:", str(e)) - return jsonify({"error": "サーバーエラー", "details": str(e)}), 500 - if __name__ == '__main__': port = int(os.environ.get("PORT", 7860)) app.run(debug=True, host="0.0.0.0", port=port) \ No newline at end of file diff --git a/feedback.html b/feedback.html new file mode 100644 index 0000000000000000000000000000000000000000..5f571e5267fcad3e3ac42ed63f67dbe930f11481 --- /dev/null +++ b/feedback.html @@ -0,0 +1,136 @@ + + +
+ + +録音した音声を送信する準備ができました。
+ + + + + diff --git a/requirements.txt b/requirements.txt index 37b3286e5b67790ce79d3e3931a53922569b16f9..eea8b236d32deaa9a919bb7dfb0134dba7aa98b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1 @@ -Flask==2.2.5 -Flask-WTF -pyannote.audio==2.1.1 -numpy==1.23.5 -pydub==0.25.1 -matplotlib==3.6.3 -python-dotenv -uwsgi -Flask-SQLAlchemy==3.0.5 -PyMySQL -Flask-Login==0.6.3 -requests==2.32.3 -google-auth==2.38.0 -google-auth-oauthlib==1.2.1 -google-auth-httplib2==0.2.0 -faster-whisper -Flask-Migrate -requests -Flask-CORS +Flask diff --git a/room.js b/room.js deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/sample.wav b/sample.wav deleted file mode 100644 index 92911c5e03f43a06fab2d7d47fabde2dcf486868..0000000000000000000000000000000000000000 --- a/sample.wav +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0f64da9fcf28836e98f50ac7a9ce3c213c186a9d444f295e5bd6a66b5b26d8c5 -size 882044 diff --git a/static/feedback.js b/static/feedback.js deleted file mode 100644 index e8e366a206304260a0278ec24d4e64bd88be4d27..0000000000000000000000000000000000000000 --- a/static/feedback.js +++ /dev/null @@ -1,66 +0,0 @@ -async function getTranscription() { - try { - const response = await fetch("/transcription"); - if (!response.ok) { - throw new Error("HTTP error! status: ${response.status}"); - } - const data = await response.json(); - const results = data.response; - } catch (error) { - console.error("Failed to fetch transcription", error); - } -} - -async function getAnalysis() { - const loader = document.getElementById("loader"); - loader.style.display = "block"; - try { - await getTranscription(); - - const response = await fetch("/analyze"); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - console.log("分析データ取得:", data); // ←構造確認用 - const results = data.results; - const analysis = results.deepseek_analysis; - - // 変数に格納 - const conversationLevel = analysis.conversationLevel; - const harassmentPresent = analysis.harassmentPresent; - const harassmentType = analysis.harassmentType; - const repetition = analysis.repetition; - const pleasantConversation = analysis.pleasantConversation; - const blameOrHarassment = analysis.blameOrHarassment; - - loader.style.display = "none"; - // DOMに表示 - document.getElementById( - "level" - ).innerText = `会話レベル: ${conversationLevel}`; - document.getElementById( - "Harassment_bool" - ).innerText = `ハラスメントの有無: ${harassmentPresent}`; - document.getElementById( - "Harassment_type" - ).innerText = `ハラスメントの種類: ${harassmentType}`; - document.getElementById( - "Harassment_loop" - ).innerText = `繰り返しの程度: ${repetition}`; - document.getElementById( - "Harassment_comfort" - ).innerText = `会話の心地よさ: ${pleasantConversation}`; - document.getElementById( - "Harassment_volume" - ).innerText = `非難またはハラスメントの程度: ${blameOrHarassment}`; - } catch (error) { - loader.style.display = "none"; - console.error("Failed to fetch analysis data:", error); - } -} - -window.onload = () => { - getAnalysis(); -}; diff --git a/static/loading.css b/static/loading.css deleted file mode 100644 index 7b41799bd554a5edb58618132319551deed3fcd4..0000000000000000000000000000000000000000 --- a/static/loading.css +++ /dev/null @@ -1,56 +0,0 @@ -.loader { - position: absolute; - top: calc(50% - 32px); - left: calc(50% - 32px); - width: 64px; - height: 64px; -} - -.loader div { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border-radius: 50%; - box-sizing: border-box; - opacity: 0.8; -} - -.one { - border-top: 1px solid #8834e8; - animation: rotate-left 1s linear infinite; -} - -.two { - border-right: 1px solid #a28ecb; - animation: rotate-right 1s linear infinite; -} - -.three { - border-bottom: 1px solid #ffd933; - animation: rotate-right 1s linear infinite; -} - -.four { - border-left: 1px solid #ff7f00; - animation: rotate-right 1s linear infinite; -} - -@keyframes rotate-left { - 0% { - transform: rotate(360deg); - } - 100% { - transform: rotate(0deg); - } -} - -@keyframes rotate-right { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/static/loading.js b/static/loading.js deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/static/main.css b/static/main.css deleted file mode 100644 index ea7842cf1909e1a61d8f7d140b7b90fccab16420..0000000000000000000000000000000000000000 --- a/static/main.css +++ /dev/null @@ -1,40 +0,0 @@ -/* Responsive Design */ -@media (max-width: 640px) { - .w-72 { - width: 95%; - } - .h-72 { - height: 350px; - } - } - /* Main Container */ - body { - background: linear-gradient(135deg, #2c3e50, #1f2937); - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - font-family: "Arial", sans-serif; - color: #fff; - } - - /* Main Content Wrapper */ - .main-content { - border: 5px solid rgba(255, 255, 255, 0.2); - padding: 2rem; - border-radius: 1rem; - width: 90%; - max-width: 500px; - background-color: rgba(0, 0, 0, 0.3); - box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4); - text-align: center; - } - - /* Title */ - .main-title { - font-size: 2.5rem; - font-weight: bold; - margin-bottom: 1.5rem; - color: #fff; - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); - } diff --git a/static/menu.css b/static/menu.css deleted file mode 100644 index 4d4c824ec410f3a9963223c3e76577a12c0a257b..0000000000000000000000000000000000000000 --- a/static/menu.css +++ /dev/null @@ -1,56 +0,0 @@ -/* Hamburger Menu Styles */ -#menu { - position: absolute; - top: 0; - left: 0; - z-index: 10; - transform: translateX(-100%); - visibility: hidden; - opacity: 0; - background-color: rgb(31, 41, 55); - transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out; - backdrop-filter: blur(10px); - border-right: 1px solid rgba(255, 255, 255, 0.2); - } - - #menu.open { - transform: translateX(0); - visibility: visible; - opacity: 1; - } - - #menu button { - transition: background-color 0.2s ease; - background-color: rgba(0, 0, 0, 0.1); - margin: 2px; - border-radius: 8px; /* 少し角を丸める */ - display: flex; - align-items: center; - justify-content: flex-start; - gap: 10px; - padding: 0.75rem 1rem; - width: 100%; - text-align: left; - border: none; - color: #fff; - font-size: 1rem; - cursor: pointer; - } - - #menu button:hover { - background-color: rgba(55, 65, 81, 0.7); - } - - /* Hamburger Menu Button */ - #menuButton { - background-color: rgba(255, 255, 255, 0.1); - border: none; - border-radius: 50%; - padding: 0.75rem; /* サイズを少し大きく */ - cursor: pointer; - transition: background-color 0.2s ease; - } - - #menuButton:hover { - background-color: rgba(255, 255, 255, 0.2); - } \ No newline at end of file diff --git a/static/menu.js b/static/menu.js deleted file mode 100644 index 3da358e8a0dbed9e9202eafc32220dbc2ca88b37..0000000000000000000000000000000000000000 --- a/static/menu.js +++ /dev/null @@ -1,53 +0,0 @@ - // Show user registration page - function showUserRegister() { - fetch("/reset"); - window.location.href = "userregister"; - } -// メンバー選択画面表示 - function showUserSelect() { - window.location.href = "/userselect"; - } - // Show recorder page - function showRecorder() { - window.location.href = "index"; - } - - // Show results page - function showResults() { - window.location.href = "feedback"; - } - - // Show talk detail page - function showTalkDetail() { - window.location.href = "talk_detail"; - } - - // Reset action page - function resetAction() { - window.location.href = "reset_html"; - } - - // Toggle hamburger menu visibility - function toggleMenu(event) { - event.stopPropagation(); // Prevents click event from propagating to the document - const menu = document.getElementById("menu"); - menu.classList.toggle("open"); - } - - // Close the menu if clicked outside - function closeMenu(event) { - const menu = document.getElementById("menu"); - if ( - menu.classList.contains("open") && - !menu.contains(event.target) && - !event.target.closest("#menuButton") - ) { - menu.classList.remove("open"); - } - } - - // Add event listener for closing the menu when clicking outside - document.addEventListener("click", closeMenu); - - // Show recorder page 名前に気を付けて! - document.getElementById("add-btn").addEventListener("click", showRecorder); \ No newline at end of file diff --git a/static/process.js b/static/process.js deleted file mode 100644 index 1254bd55f3ecf43f253c96b135b12115d6a51df6..0000000000000000000000000000000000000000 --- a/static/process.js +++ /dev/null @@ -1,222 +0,0 @@ - - let isRecording = false; - let mediaRecorder; - let audioChunks = []; - let recordingInterval; - let count_voice = 0; - let before_rate = []; - const RECORDING_INTERVAL_MS = 5000; // 5秒 - // メンバーとチャートの初期化 - let members = []; - let voiceData = []; - let baseMemberColors = ["#4caf50", "#007bff", "#ffc107", "#dc3545", "#28a745", "#9c27b0", "#ff9800"]; - // Chart.js の初期化 - const ctx = document.getElementById("speechChart").getContext("2d"); - const speechChart = new Chart(ctx, { - type: "doughnut", - data: { - labels: members, - datasets: [ - { - data: voiceData, - backgroundColor: getMemberColors(members.length), - }, - ], - }, - options: { - responsive: true, - plugins: { - legend: { - display: true, - position: "bottom", - labels: { color: "white" }, - }, - }, - }, - }); - // サーバーからメンバー情報を取得してチャートを更新する関数 - async function updateChartFrom() { - try { - const response = await fetch("/confirm"); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - if (!data || !data.members || !Array.isArray(data.members)) { - console.error("Invalid member data received:", data); - members = ["member1"]; - voiceData = [50, 50]; - updateChart(); - return; - } - members = data.members; - voiceData = []; - for (let i = 0; i < members.length; i++) { - voiceData.push(100 / members.length); - } - updateChart(); - } catch (error) { - console.error("Failed to fetch member data:", error); - members = ["member1"]; - voiceData = [50, 50]; - updateChart(); - } - } - function updateChart() { - // 一人モードの場合は、ユーザーとグレー(無音)の比率をチャートに表示 - if (members.length === 1) { - const userName = members[0]; - speechChart.data.labels = [userName, "無音"]; - speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"]; - } else { - // 複数メンバーの場合は通常通りの処理 - speechChart.data.labels = members; - speechChart.data.datasets[0].backgroundColor = getMemberColors(members.length); - } - speechChart.data.datasets[0].data = voiceData; - speechChart.update(); - } - - // ページ読み込み時にチャート情報を更新 - updateChartFrom(); - // 録音ボタンの録音開始/停止処理 - async function toggleRecording() { - const recordButton = document.getElementById("recordButton"); - if (!isRecording) { - // 録音開始 - isRecording = true; - recordButton.classList.add("recording"); - try { - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - mediaRecorder = new MediaRecorder(stream); - audioChunks = []; - mediaRecorder.ondataavailable = (event) => { - if (event.data.size > 0) { - audioChunks.push(event.data); - } - }; - mediaRecorder.onstop = () => { - sendAudioChunks([...audioChunks]); - audioChunks = []; - }; - mediaRecorder.start(); - // 5秒ごとに録音を停止して送信するインターバルを設定 - recordingInterval = setInterval(() => { - if (mediaRecorder && mediaRecorder.state === "recording") { - mediaRecorder.stop(); - mediaRecorder.start(); - } - }, RECORDING_INTERVAL_MS); - } catch (error) { - console.error("マイクへのアクセスに失敗しました:", error); - isRecording = false; - recordButton.classList.remove("recording"); - } - } else { - // 録音停止 - isRecording = false; - recordButton.classList.remove("recording"); - if (mediaRecorder && mediaRecorder.state === "recording") { - mediaRecorder.stop(); - } - clearInterval(recordingInterval); - count_voice = 0; - //before_rate = []; - } - } - function sendAudioChunks(chunks) { - const audioBlob = new Blob(chunks, { type: "audio/wav" }); - const reader = new FileReader(); - reader.onloadend = () => { - const base64String = reader.result.split(",")[1]; - const form = document.getElementById("recordForm"); - const nameInput = form.querySelector('input[name="name"]'); - const name = nameInput ? nameInput.value : "unknown"; - fetch("/upload_audio", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ audio_data: base64String, name: name }), - }) - .then((response) => response.json()) - .then((data) => { - if (data.error) { - alert("エラー: " + data.error); - console.error(data.details); - } else if (data.rate !== undefined) { - updateChartData(data.rate); - } else if (data.rates !== undefined) { - updateChartData(data.rates); - } - }) - .catch((error) => { - console.error("エラー:", error); - }); - }; - reader.readAsDataURL(audioBlob); - } - function getMemberColors(memberCount) { - // 一人モードの場合は特別な処理をしない(updateChartで処理するため) - if (memberCount <= 1) { - return ["#4caf50", "#757575"]; - } else { - let colors = []; - for (let i = 0; i < memberCount; i++) { - colors.push(baseMemberColors[i % baseMemberColors.length]); - } - return colors; - } - } - function updateChartData(newRate) { - // 一人モードの時の処理 - if (members.length === 1) { - if (count_voice === 0) { - speechChart.data.datasets[0].data = [newRate, 100 - newRate]; - before_rate = [newRate]; - } else { - // 一人モードでは、過去のデータと現在のデータを加重平均する - let tmp_rate = (newRate + before_rate[0] * count_voice) / (count_voice + 1); - speechChart.data.datasets[0].data = [tmp_rate, 100 - tmp_rate]; - before_rate = [tmp_rate]; - } - count_voice++; - // 一人モードでは常に緑色とグレーの組み合わせを使用 - speechChart.data.labels = [members[0], "無音"]; - speechChart.data.datasets[0].backgroundColor = ["#4caf50", "#757575"]; - } else { - console.log(before_rate) - // 複数人モードの処理 - if (!Array.isArray(newRate)) { - console.error("newRate is not an array:", newRate); - return; - } - if (newRate.length !== members.length) { - console.error( - "newRate length does not match members length:", - newRate, - members - ); - return; - } - let averagedRates = new Array(newRate.length); - for (let i = 0; i < newRate.length; i++) { - let tmp_rate; - if (count_voice === 0) { - // 初回はそのまま - tmp_rate = newRate[i]; - } else { - // 2回目以降は、過去の平均値と現在の値を加重平均する - tmp_rate = (newRate[i] + before_rate[i] * count_voice) / (count_voice + 1); - } - averagedRates[i] = tmp_rate; - } - // before_rateを更新 - before_rate = averagedRates; - //グラフに反映 - speechChart.data.datasets[0].data = averagedRates; - count_voice++; - speechChart.data.datasets[0].backgroundColor = getMemberColors( - members.length - ); - } - speechChart.update(); - } \ No newline at end of file diff --git a/static/process1.js b/static/process1.js deleted file mode 100644 index 9bce589a125108d6d140487ae8ec1234e734ef3b..0000000000000000000000000000000000000000 --- a/static/process1.js +++ /dev/null @@ -1,294 +0,0 @@ -let allUsers = []; -let selectedUsers = []; -let userToDelete = null; - -// ページ読み込み時にユーザーリストを取得 -document.addEventListener('DOMContentLoaded', fetchUserList); - -// ユーザーリスト取得 - Flask APIの変更に合わせて修正 -function fetchUserList() { - fetch('/list_base_audio') - .then(response => response.json()) - .then(data => { - if (data.status === 'success' && Array.isArray(data.id)) { - // APIの変更: data.fileNames → data.id - allUsers = data.id; - renderUserList(allUsers); - } else { - showError('メンバーリストの取得に失敗しました'); - } - }) - .catch(error => { - console.error('Error fetching user list:', error); - showError('サーバーとの通信中にエラーが発生しました'); - }); -} - -// ユーザーリストの表示 -function renderUserList(users) { - const userListElement = document.getElementById('userList'); - - if (!users || users.length === 0) { - userListElement.innerHTML = ` -登録されているメンバーがいません。
-「新規登録」から音声を登録してください。
-${message}
- -メンバー「${userToDelete}」を削除中...
-メンバーリストを読み込み中...
-