Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import re | |
| import os | |
| from groq import Groq | |
| from utils import load_system_prompt, extract_all_code_blocks, is_physics_related, save_generated_code_to_new_file, get_file_dependencies # get_file_dependenciesをインポート | |
| def setup_chat(api_key, mobile=False): | |
| """チャット機能とその関連機能をセットアップする""" | |
| # ヘッダー | |
| st.header("AIアシスタント" if not mobile else "チャット") | |
| # 過去のメッセージを表示 | |
| display_message_history() | |
| # 明示的に更新する | |
| if st.session_state.get("ui_needs_update", False) or True: # Trueを追加して常に表示 | |
| # 思考プロセスの表示(開閉可能なエクスパンダー) | |
| if st.session_state.thinking_process: | |
| with st.expander("思考プロセスを表示"): | |
| for i, thought in enumerate(st.session_state.thinking_process): | |
| st.subheader(thought["title"]) | |
| st.markdown(thought["content"]) | |
| st.divider() | |
| # 最後に生成されたコードを保存する機能 | |
| if st.session_state.last_generated_code: | |
| if st.button("AIが生成したコードをエディタに保存", key="save_ai_code"): | |
| with st.spinner("コードを保存中..."): | |
| file_name = save_generated_code_to_new_file(st.session_state.last_generated_code) | |
| st.success(f"コードを '{file_name}' として保存しました") | |
| st.session_state.last_generated_code = None # 保存したのでクリア | |
| # モバイルモードならエディタタブに切り替え | |
| if mobile: | |
| st.session_state.active_tab = "エディタ" | |
| st.rerun() | |
| # フラグをリセット | |
| st.session_state.ui_needs_update = False | |
| # チャット入力 | |
| if prompt := st.chat_input("質問を入力してください"): | |
| process_chat_input(prompt, api_key) | |
| def display_message_history(): | |
| """会話履歴を表示する""" | |
| for message in st.session_state.messages: | |
| with st.chat_message(message["role"]): | |
| st.markdown(message["content"]) | |
| def process_chat_input(prompt, api_key): | |
| """ユーザー入力を処理し、応答を生成する""" | |
| # ユーザーメッセージを表示 | |
| st.chat_message("user").markdown(prompt) | |
| # ユーザーメッセージをセッション状態に追加 | |
| st.session_state.messages.append({"role": "user", "content": prompt}) | |
| # 編集コマンドを検出 | |
| if prompt.strip().startswith("/edit"): | |
| process_edit_command(prompt, api_key) | |
| return | |
| # モード切替コマンドを検出 | |
| if prompt.strip().startswith("/mode"): | |
| process_mode_command(prompt) | |
| return | |
| # ファイル解析コマンドを検出 | |
| if prompt.strip().startswith("/analyse") or prompt.strip().startswith("/analyze"): | |
| process_analyze_command(api_key) | |
| return | |
| # 通常の質問処理 | |
| process_normal_query(prompt, api_key) | |
| def process_edit_command(prompt, api_key): | |
| """エディタのコード編集コマンドを処理する""" | |
| # 現在開いているファイルがない場合 | |
| if not st.session_state.current_file: | |
| response = "エディタに開いているファイルがありません。まずはファイルを作成するか開いてください。" | |
| st.chat_message("assistant").markdown(response) | |
| st.session_state.messages.append({"role": "assistant", "content": response}) | |
| return | |
| # 編集指示を抽出 | |
| instructions = prompt.replace("/edit", "").strip() | |
| if not instructions: | |
| instructions = "コードを改善してください。可読性を高め、バグがあれば修正し、コメントを追加してください。" | |
| current_code = st.session_state.files[st.session_state.current_file] | |
| file_ext = os.path.splitext(st.session_state.current_file)[1].lower() | |
| # 言語を特定 | |
| lang_map = { | |
| ".py": "Python", | |
| ".js": "JavaScript", | |
| ".html": "HTML", | |
| ".css": "CSS", | |
| ".json": "JSON", | |
| ".txt": "テキスト" | |
| } | |
| language = lang_map.get(file_ext, "コード") | |
| # AIに編集を依頼 | |
| with st.spinner("AIによるコード編集中..."): | |
| edited_code = request_ai_edit( | |
| api_key, | |
| current_code, | |
| instructions, | |
| language | |
| ) | |
| if edited_code: | |
| st.session_state.files[st.session_state.current_file] = edited_code | |
| response = f"✅ {st.session_state.current_file} を編集しました。編集結果はエディタで確認できます。" | |
| # モバイルモードならエディタタブに切り替え | |
| if st.session_state.mobile_mode: | |
| st.session_state.active_tab = "エディタ" | |
| else: | |
| response = "⚠️ コードの編集に失敗しました。もう一度お試しください。" | |
| # 応答を表示して保存 | |
| st.chat_message("assistant").markdown(response) | |
| st.session_state.messages.append({"role": "assistant", "content": response}) | |
| if st.session_state.mobile_mode and edited_code: | |
| st.rerun() | |
| def process_mode_command(prompt): | |
| """モード切り替えコマンドを処理する""" | |
| parts = prompt.strip().split() | |
| if len(parts) < 2: | |
| response = "モード指定が不完全です。使用方法: /mode [通常|実装|エラー修正]" | |
| else: | |
| mode = parts[1].lower() | |
| valid_modes = {"通常": "通常モード", "実装": "実装モード", "エラー修正": "エラー修正モード"} | |
| if mode not in valid_modes and mode not in valid_modes.values(): | |
| response = f"無効なモードです。使用可能なモード: {', '.join(valid_modes.keys())}" | |
| else: | |
| old_mode = st.session_state.current_mode | |
| if mode in valid_modes: | |
| st.session_state.current_mode = valid_modes[mode] | |
| else: | |
| st.session_state.current_mode = mode | |
| st.session_state.is_first_message = True | |
| # モード変更時の説明メッセージを用意 | |
| mode_descriptions = { | |
| "通常モード": "通常の質問応答モードに切り替えました。", | |
| "実装モード": "アイデアから設計と実装を生成するモードに切り替えました。", | |
| "エラー修正モード": "エラーメッセージから問題を診断・修正するモードに切り替えました。" | |
| } | |
| instructions = { | |
| "通常モード": "質問や議論したいトピックを入力してください。", | |
| "実装モード": "実装したい機能やアイデアを入力してください。", | |
| "エラー修正モード": "発生したエラーメッセージとコードを入力してください。" | |
| } | |
| response = f"モードを「{old_mode}」から「{st.session_state.current_mode}」に変更しました。\n\n{mode_descriptions[st.session_state.current_mode]}\n\n{instructions[st.session_state.current_mode]}" | |
| # レスポンスを表示 | |
| st.chat_message("assistant").markdown(response) | |
| # アシスタントメッセージをセッション状態に追加 | |
| st.session_state.messages.append({"role": "assistant", "content": response}) | |
| def process_analyze_command(api_key): | |
| """アップロードされたファイル群を解析するコマンドを処理する""" | |
| # ファイルが存在するか確認 | |
| if not st.session_state.files: | |
| response = "解析するファイルがありません。まずはファイルをアップロードするか作成してください。" | |
| st.chat_message("assistant").markdown(response) | |
| st.session_state.messages.append({"role": "assistant", "content": response}) | |
| return | |
| with st.spinner("ファイル群を解析中..."): | |
| # 依存関係の分析を実行 | |
| dependencies, file_summary = get_file_dependencies(st.session_state.files) | |
| # 解析結果を生成 | |
| all_files_content = "" | |
| for filename, content in st.session_state.files.items(): | |
| # ファイルサイズが大きい場合は先頭部分のみ | |
| if len(content) > 1000: | |
| all_files_content += f"\n\nファイル '{filename}'(先頭部分):\n```\n{content[:1000]}...\n```" | |
| else: | |
| all_files_content += f"\n\nファイル '{filename}':\n```\n{content}\n```" | |
| # AIに解析を依頼 | |
| client = Groq(api_key=api_key) | |
| # モデル名のマッピング | |
| from config import MODELS | |
| system_prompt = load_system_prompt(MODELS["qwen_coder"], "通常モード") | |
| system_prompt += """ | |
| 複数のファイルからなるプロジェクトを解析する際は、以下の点に注目してください: | |
| 1. ファイル間の依存関係と呼び出し構造 | |
| 2. 主要なクラスやモジュールの責任範囲 | |
| 3. データの流れと状態管理の方法 | |
| 4. アプリケーションのアーキテクチャパターン | |
| 5. コードの再利用性と保守性 | |
| 解析結果は以下の構造で提供してください: | |
| 1. プロジェクト概要 - 全体の目的と機能 | |
| 2. 主要コンポーネント - 各ファイルの役割と責任 | |
| 3. 依存関係図 - ファイル間の関係性 | |
| 4. アーキテクチャ評価 - 設計パターンと原則の適用状況 | |
| 5. 改善提案 - リファクタリングや拡張のための提案 | |
| """ | |
| analyze_prompt = f""" | |
| 以下のファイル群からなるプロジェクトの構造と依存関係を分析してください。 | |
| 各ファイルの役割、相互関係、データフローを明確にし、システム全体の理解を深めるための | |
| 包括的な分析を提供してください。 | |
| ファイル依存関係の分析結果: | |
| {dependencies} | |
| ファイル概要: | |
| {file_summary} | |
| アップロードされたファイル群の内容(一部省略あり): | |
| {all_files_content} | |
| """ | |
| # 回答の生成 | |
| response = client.chat.completions.create( | |
| model=MODELS["qwen_coder"], | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": analyze_prompt} | |
| ], | |
| temperature=0.5, | |
| max_tokens=4000 | |
| ) | |
| analysis_result = response.choices[0].message.content | |
| # 分析結果を思考プロセスに追加 | |
| st.session_state.thinking_process.append({ | |
| "title": "プロジェクト構造解析", | |
| "content": f"依存関係:\n{dependencies}\n\nファイル概要:\n{file_summary}" | |
| }) | |
| # レスポンスを表示 | |
| st.chat_message("assistant").markdown(analysis_result) | |
| # アシスタントメッセージをセッション状態に追加 | |
| st.session_state.messages.append({"role": "assistant", "content": analysis_result}) | |
| # 明示的に表示を更新するために変数を設定 | |
| st.session_state.ui_needs_update = True | |
| st.rerun() | |
| def process_normal_query(prompt, api_key): | |
| """通常の質問を処理し、AIからの応答を生成する""" | |
| # APIキーがある場合のみ実行 | |
| if api_key: | |
| try: | |
| # 最初のメッセージはチェーンを使用、それ以降はQwen Coderのみで応答 | |
| if st.session_state.is_first_message: | |
| with st.spinner("回答を生成中...(複数のモデルによる分析を行っています)"): | |
| response, thinking_results = generate_chain_response(prompt, api_key) | |
| st.session_state.thinking_process = thinking_results | |
| st.session_state.is_first_message = False | |
| else: | |
| with st.spinner("回答を生成中..."): | |
| response = generate_direct_response(prompt, api_key) | |
| # レスポンスを表示 | |
| st.chat_message("assistant").markdown(response) | |
| # アシスタントメッセージをセッション状態に追加 | |
| st.session_state.messages.append({"role": "assistant", "content": response}) | |
| # 会話状態を更新 | |
| update_conversation_state(prompt, response) | |
| # モード切替提案を追加(UIには表示しない) | |
| mode_suggestions = { | |
| "通常モード": "\n\n---\n*実装を生成するには、「/mode 実装」と入力して実装モードに切り替えてください。*", | |
| "実装モード": "\n\n---\n*このコードを検証・修正するには、「/mode エラー修正」と入力して検証修正モードに切り替えてください。*", | |
| "エラー修正モード": "\n\n---\n*新しい実装を生成するには、「/mode 実装」と入力して実装モードに切り替えてください。*" | |
| } | |
| # 提案はセッションステートには保存するが、UIには表示しない(次回のcontext生成で使用) | |
| suggestion = mode_suggestions.get(st.session_state.current_mode, "") | |
| st.session_state.messages[-1]["content"] += suggestion | |
| # コードブロックを抽出して保存機能を提供 | |
| code_blocks = extract_all_code_blocks(response) | |
| if code_blocks and len(code_blocks) > 0: | |
| st.session_state.last_generated_code = code_blocks[0] | |
| # 明示的に表示を更新するために変数を設定 | |
| st.session_state.ui_needs_update = True | |
| # モバイルでは自動的にUI更新のためのフラグを設定 | |
| if st.session_state.mobile_mode: | |
| st.rerun() # モバイルでは即座に画面を更新 | |
| except Exception as e: | |
| st.error(f"エラーが発生しました: {str(e)}") | |
| else: | |
| st.error("GROQ_API_KEYが設定されていません。") | |
| def generate_chain_response(user_input, api_key): | |
| """複数のモデルを使ったチェーンベースの応答生成""" | |
| client = Groq(api_key=api_key) | |
| thinking_results = [] | |
| # モデル名のマッピング | |
| from config import MODELS | |
| # ユーザー入力が物理関連かどうかをチェック | |
| is_physics = is_physics_related(user_input) | |
| # Step 1: Mistral Sabaによる入力分析 | |
| mistral_system_prompt = load_system_prompt(MODELS["mistral_saba"]) | |
| mistral_prompt = f""" | |
| 分析以下用户输入。 提取用户问题或请求的性质、关键词和技术问题、 | |
| 详细考虑应采用何种方法来回答这些问题。 | |
| ユーザー入力: {user_input} | |
| """ | |
| mistral_response = client.chat.completions.create( | |
| model=MODELS["mistral_saba"], | |
| messages=[ | |
| {"role": "system", "content": mistral_system_prompt}, | |
| {"role": "user", "content": mistral_prompt} | |
| ], | |
| temperature=0.6, | |
| max_tokens=3000 | |
| ) | |
| mistral_analysis = mistral_response.choices[0].message.content | |
| thinking_results.append({ | |
| "title": "Mistral Sabaによる分析", | |
| "content": mistral_analysis | |
| }) | |
| # 物理関連の場合のみDeepSeekの意見を取得 | |
| deepseek_knowledge = "" | |
| if is_physics: | |
| deepseek_system_prompt = load_system_prompt(MODELS["deepseek"]) | |
| deepseek_prompt = f""" | |
| 以下のユーザー入力は物理学に関連しています。物理学の観点から、この質問や課題に対する | |
| 科学的知識、法則、公式、アプローチを提供してください。特に関連する物理概念と実装方法の | |
| 関連性があれば説明してください。 | |
| ユーザー入力: {user_input} | |
| """ | |
| deepseek_response = client.chat.completions.create( | |
| model=MODELS["deepseek"], | |
| messages=[ | |
| {"role": "system", "content": deepseek_system_prompt}, | |
| {"role": "user", "content": deepseek_prompt} | |
| ], | |
| temperature=0.6, | |
| max_tokens=3000 | |
| ) | |
| deepseek_knowledge = deepseek_response.choices[0].message.content | |
| thinking_results.append({ | |
| "title": "DeepSeekによる物理学的知見", | |
| "content": deepseek_knowledge | |
| }) | |
| # Step 2: モード別の応答生成 | |
| if st.session_state.current_mode == "通常モード": | |
| # 物理関連の場合のみDeepSeekの情報を含める | |
| deepseek_section = "" | |
| physics_consideration = "" | |
| if is_physics: | |
| physics_consideration = " DeepSeekの物理学的知見を考慮して" | |
| deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}" | |
| # 現在のエディタのコードを取得(コードエディタで何かが開かれている場合) | |
| editor_code = "" | |
| editor_reference = "" | |
| if st.session_state.current_file and st.session_state.current_file in st.session_state.files: | |
| editor_code = st.session_state.files[st.session_state.current_file] | |
| editor_reference = f"現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```" | |
| # アップロードされた全ファイルのリストを作成 | |
| all_files_context = "" | |
| mentioned_files = [] | |
| # ユーザー入力で言及されたファイル名を抽出 | |
| for filename in st.session_state.files.keys(): | |
| if filename in user_input and filename != st.session_state.current_file: | |
| mentioned_files.append(filename) | |
| # ファイルコンテキストの生成 | |
| if mentioned_files: | |
| all_files_context = "ユーザーが言及したファイル:\n" | |
| for filename in mentioned_files: | |
| file_content = st.session_state.files[filename] | |
| # ファイルサイズが大きい場合は先頭部分のみ含める | |
| if len(file_content) > 1500: | |
| all_files_context += f"\nファイル '{filename}'(先頭部分):\n```\n{file_content[:1500]}...\n```" | |
| else: | |
| all_files_context += f"\nファイル '{filename}':\n```\n{file_content}\n```" | |
| elif len(st.session_state.files) > 1: # 複数ファイルがある場合 | |
| all_files_context = "アップロードされたファイル一覧:\n" | |
| for filename in st.session_state.files.keys(): | |
| if filename != st.session_state.current_file: # 現在開いているファイル以外 | |
| all_files_context += f"- {filename}\n" | |
| # 通常モード用のシステムプロンプトをロード | |
| system_prompt = load_system_prompt(MODELS["qwen_coder"], "通常モード") | |
| qwen_prompt = f""" | |
| 以下のユーザー入力に対してコーディングアシスタントとして回答してください。 | |
| Mistral Sabaの分析結果{physics_consideration}を考慮して、 | |
| 最適なソリューションを提供してください。 | |
| ユーザー入力: {user_input} | |
| Mistral Sabaの分析: | |
| {mistral_analysis} | |
| """ | |
| # 物理関連の場合のみDeepSeekの情報を追加 | |
| if is_physics: | |
| qwen_prompt += f"\n\n{deepseek_section}" | |
| # エディタのコードが存在する場合は参照として追加 | |
| if editor_code: | |
| qwen_prompt += f"\n\n{editor_reference}\n\nエディタで開かれているコードに関して、ユーザーの質問に答えるか、必要に応じてコードの改善を提案してください。" | |
| # アップロードされたファイル情報を追加 | |
| if all_files_context: | |
| qwen_prompt += f"\n\n{all_files_context}" | |
| qwen_response = client.chat.completions.create( | |
| model=MODELS["qwen_coder"], | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": qwen_prompt} | |
| ], | |
| temperature=0.5, | |
| max_tokens=4000 | |
| ) | |
| final_response = qwen_response.choices[0].message.content | |
| elif st.session_state.current_mode == "実装モード": | |
| # 物理関連の条件分岐を変数で処理 | |
| deepseek_section = "" | |
| physics_note = "" | |
| if is_physics: | |
| deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}" | |
| physics_note = "この実装には物理シミュレーションが含まれているため、物理法則の正確性と数値的安定性を特に重視してください。" | |
| # アップロードされたファイル情報を取得 | |
| all_files_context = "" | |
| mentioned_files = [] | |
| # ユーザー入力で言及されたファイル名を抽出 | |
| for filename in st.session_state.files.keys(): | |
| if filename in user_input: | |
| mentioned_files.append(filename) | |
| # ファイルコンテキストの生成 | |
| if mentioned_files: | |
| all_files_context = "参照すべきファイル:\n" | |
| for filename in mentioned_files: | |
| file_content = st.session_state.files[filename] | |
| # ファイルサイズが大きい場合は先頭部分のみ含める | |
| if len(file_content) > 1000: | |
| all_files_context += f"\nファイル '{filename}'(先頭部分):\n```\n{file_content[:1000]}...\n```" | |
| else: | |
| all_files_context += f"\nファイル '{filename}':\n```\n{file_content}\n```" | |
| # Step 2a: 設計フェーズ - 専用のプロンプトを使用 | |
| design_system_prompt = load_system_prompt(MODELS["qwen_coder"], "実装モード_設計") | |
| design_prompt = f""" | |
| 以下のユーザー入力と分析結果に基づいて、実装に必要な設計書を作成してください。 | |
| ユーザー入力: {user_input} | |
| Mistral Sabaの分析: | |
| {mistral_analysis} | |
| """ | |
| # 物理関連の場合のみDeepSeekの情報を追加 | |
| if is_physics: | |
| design_prompt += f"\n\n{deepseek_section}" | |
| # アップロードされたファイル情報を追加 | |
| if all_files_context: | |
| design_prompt += f"\n\n{all_files_context}\n\n上記ファイルの構造や機能を考慮して設計を行ってください。" | |
| design_response = client.chat.completions.create( | |
| model=MODELS["qwen_coder"], | |
| messages=[ | |
| {"role": "system", "content": design_system_prompt}, | |
| {"role": "user", "content": design_prompt} | |
| ], | |
| temperature=0.6, | |
| max_tokens=4000 | |
| ) | |
| design_doc = design_response.choices[0].message.content | |
| thinking_results.append({ | |
| "title": "Qwen Coderによる設計書", | |
| "content": design_doc | |
| }) | |
| # Step 2b: 実装フェーズ - 専用のプロンプトを使用 | |
| implementation_system_prompt = load_system_prompt(MODELS["qwen_coder"], "実装モード_実装") | |
| implementation_prompt = f""" | |
| 以下の設計書に基づいて、完全な実装コードを提供してください。 | |
| コードは実行可能で、エラー処理が適切に行われているものにしてください。 | |
| 各コンポーネントには十分なコメントを付け、使用方法の例も含めてください。 | |
| {physics_note} | |
| 設計書: | |
| {design_doc} | |
| ユーザーの元々の要求: | |
| {user_input} | |
| """ | |
| # アップロードされたファイル情報を追加(実装フェーズでも必要) | |
| if all_files_context: | |
| implementation_prompt += f"\n\n{all_files_context}\n\n上記の既存ファイルと互換性のあるコードを生成してください。" | |
| implementation_response = client.chat.completions.create( | |
| model=MODELS["qwen_coder"], | |
| messages=[ | |
| {"role": "system", "content": implementation_system_prompt}, | |
| {"role": "user", "content": implementation_prompt} | |
| ], | |
| temperature=0.5, | |
| max_tokens=4000 | |
| ) | |
| implementation_result = implementation_response.choices[0].message.content | |
| # 設計書と実装を組み合わせた最終結果 | |
| final_response = f"# 設計書\n\n{design_doc}\n\n# 実装コード\n\n{implementation_result}" | |
| # コードの自動抽出と保存機能 | |
| code_blocks = extract_all_code_blocks(implementation_result) | |
| if code_blocks and len(code_blocks) > 0: | |
| # 抽出したコードブロックに対してボタンを作成するための識別子を付加 | |
| final_response += "\n\n**実装されたコードをエディタに保存できます。チャット上の「コードを保存」ボタンをクリックしてください。**" | |
| # 最初のコードブロックを抽出してセッション状態に保存 | |
| st.session_state.last_generated_code = code_blocks[0] | |
| elif st.session_state.current_mode == "エラー修正モード": | |
| # エディタのコードを取得(存在する場合) | |
| editor_code = "" | |
| editor_reference = "" | |
| if st.session_state.current_file and st.session_state.current_file in st.session_state.files: | |
| editor_code = st.session_state.files[st.session_state.current_file] | |
| editor_reference = f"現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```\n\nこのコードにエラーがある可能性があります。ユーザーからの情報と合わせて分析してください。" | |
| # 物理関連の条件分岐を変数で処理 | |
| deepseek_section = "" | |
| if is_physics: | |
| deepseek_section = f"DeepSeekの物理学的知見:\n{deepseek_knowledge}" | |
| # アップロードされたファイル情報を取得 | |
| all_files_context = "" | |
| mentioned_files = [] | |
| # ユーザー入力で言及されたファイル名を抽出 | |
| for filename in st.session_state.files.keys(): | |
| if filename in user_input and filename != st.session_state.current_file: | |
| mentioned_files.append(filename) | |
| # ファイルコンテキストの生成 | |
| if mentioned_files: | |
| all_files_context = "関連するファイル:\n" | |
| for filename in mentioned_files: | |
| file_content = st.session_state.files[filename] | |
| # ファイルサイズが大きい場合は先頭部分のみ含める | |
| if len(file_content) > 1000: | |
| all_files_context += f"\nファイル '{filename}'(先頭部分):\n```\n{file_content[:1000]}...\n```" | |
| else: | |
| all_files_context += f"\nファイル '{filename}':\n```\n{file_content}\n```" | |
| # エラー修正モード用のシステムプロンプトをロード | |
| error_system_prompt = load_system_prompt(MODELS["qwen_coder"], "エラー修正モード") | |
| error_prompt = f""" | |
| 以下のユーザー入力はコードのエラーに関するものです。 | |
| エラーの原因を特定し、修正案を提供してください。 | |
| 説明では: | |
| 1. エラーの根本原因 | |
| 2. 問題箇所の特定 | |
| 3. 修正方法の詳細な説明 | |
| 4. 修正されたコード | |
| 5. 同様のエラーを防ぐためのベストプラクティス | |
| を含めてください。 | |
| ユーザー入力: {user_input} | |
| Mistral Sabaの分析: | |
| {mistral_analysis} | |
| """ | |
| # エディタのコードを追加 | |
| if editor_code: | |
| error_prompt += f"\n\n{editor_reference}" | |
| # 物理関連の場合のみDeepSeekの情報を追加 | |
| if is_physics: | |
| error_prompt += f"\n\n{deepseek_section}" | |
| # 関連ファイル情報を追加 | |
| if all_files_context: | |
| error_prompt += f"\n\n{all_files_context}" | |
| error_response = client.chat.completions.create( | |
| model=MODELS["qwen_coder"], | |
| messages=[ | |
| {"role": "system", "content": error_system_prompt}, | |
| {"role": "user", "content": error_prompt} | |
| ], | |
| temperature=0.6, | |
| max_tokens=4000 | |
| ) | |
| final_response = error_response.choices[0].message.content | |
| return final_response, thinking_results | |
| def generate_direct_response(user_input, api_key): | |
| """会話の連続性を考慮した直接応答を生成する""" | |
| # APIクライアントを初期化 | |
| client = Groq(api_key=api_key) | |
| # モデル名のマッピング | |
| from config import MODELS | |
| # 現在のモードに合わせたシステムプロンプトをロード | |
| current_system_prompt = load_system_prompt(MODELS["qwen_coder"], st.session_state.current_mode) | |
| # 会話の連続性のための追加指示 | |
| current_system_prompt += """ | |
| 重要: これは継続中の会話です。前の質問や回答を踏まえて、会話の自然な流れを維持してください。 | |
| ユーザーの最新の質問だけに集中するのではなく、会話全体の文脈を考慮してください。 | |
| 繰り返しは避け、新しい情報や洞察を提供してください。 | |
| """ | |
| # 過去の会話履歴を完全に構築 | |
| context = "" | |
| if len(st.session_state.messages) > 2: | |
| # 最近の会話履歴(最大トークン制限を考慮して適切な数に制限) | |
| recent_messages = st.session_state.messages[-10:] # 最近の10メッセージまで | |
| for msg in recent_messages: | |
| role = "ユーザー" if msg["role"] == "user" else "アシスタント" | |
| # 全文を含める | |
| context += f"{role}: {msg['content']}\n\n" | |
| # 現在のエディタのコードを取得(存在する場合) | |
| editor_code = "" | |
| editor_reference = "" | |
| if st.session_state.current_file and st.session_state.current_file in st.session_state.files: | |
| editor_code = st.session_state.files[st.session_state.current_file] | |
| editor_reference = f"\n\n現在エディタで開かれているファイル '{st.session_state.current_file}':\n```\n{editor_code}\n```\n\nエディタで開かれているコードに関して、ユーザーの質問に答えるか、必要に応じてコードの改善を提案してください。" | |
| # ユーザー入力で言及されたファイルに関する情報を追加 | |
| all_files_context = "" | |
| mentioned_files = [] | |
| # ユーザー入力で言及されたファイル名を抽出 | |
| for filename in st.session_state.files.keys(): | |
| if filename in user_input and filename != st.session_state.current_file: | |
| mentioned_files.append(filename) | |
| # ファイルコンテキストの生成 | |
| if mentioned_files: | |
| all_files_context = "\n\nユーザーが言及したファイル:\n" | |
| for filename in mentioned_files: | |
| file_content = st.session_state.files[filename] | |
| # ファイルサイズが大きい場合は先頭部分のみ含める | |
| if len(file_content) > 1000: | |
| all_files_context += f"\nファイル '{filename}'(先頭部分):\n```\n{file_content[:1000]}...\n```" | |
| else: | |
| all_files_context += f"\nファイル '{filename}':\n```\n{file_content}\n```" | |
| qwen_prompt = f""" | |
| あなたはユーザーと継続的な会話を行っているアシスタントです。 | |
| これまでの会話の流れを踏まえて、最新のユーザー入力に適切に応答してください。 | |
| これまでの会話: | |
| {context} | |
| 最新のユーザー入力: {user_input} | |
| 前の会話の文脈を考慮して、この最新の質問に直接回答してください。 | |
| 前回までの内容を繰り返すのではなく、会話を自然に進めてください。 | |
| """ | |
| # エディタのコードが存在する場合は参照として追加 | |
| if editor_code: | |
| qwen_prompt += editor_reference | |
| # 言及されたファイルがある場合はコンテキストに追加 | |
| if all_files_context: | |
| qwen_prompt += all_files_context | |
| qwen_response = client.chat.completions.create( | |
| model=MODELS["qwen_coder"], | |
| messages=[ | |
| {"role": "system", "content": current_system_prompt}, | |
| {"role": "user", "content": qwen_prompt} | |
| ], | |
| temperature=0.5, | |
| max_tokens=4000 | |
| ) | |
| return qwen_response.choices[0].message.content | |
| def request_ai_edit(api_key, code, instructions, language): | |
| """AIにコードの編集を依頼する""" | |
| client = Groq(api_key=api_key) | |
| # モデル名のマッピング | |
| from config import MODELS | |
| # 編集用のシステムプロンプトをロード | |
| edit_system_prompt = """あなたは優秀なコードエディタアシスタントです。 | |
| ユーザーから提供されたコードを指示に従って編集する役割を担っています。 | |
| 以下のガイドラインに従ってください: | |
| 1. コード全体を提供してください(一部だけではなく) | |
| 2. 元のコードの機能を維持してください | |
| 3. コメントを追加して読みやすくします | |
| 4. ベストプラクティスに従ってコードを改善します | |
| 5. パフォーマンスやセキュリティの問題があれば修正します | |
| 最終的な編集済みコードのみを返してください。説明や解説は含めないでください。 | |
| """ | |
| edit_prompt = f""" | |
| 以下の{language}コードを編集してください。 | |
| 編集指示: | |
| {instructions} | |
| コード: | |
| ``` | |
| {code} | |
| ``` | |
| 編集後のコード全体を返してください。解説は不要です。 | |
| """ | |
| try: | |
| response = client.chat.completions.create( | |
| model=MODELS["qwen_coder"], | |
| messages=[ | |
| {"role": "system", "content": edit_system_prompt}, | |
| {"role": "user", "content": edit_prompt} | |
| ], | |
| temperature=0.3, | |
| max_tokens=4000 | |
| ) | |
| # 回答からコードを抽出 | |
| edited_code = response.choices[0].message.content | |
| # コードブロックから実際のコードを抽出 | |
| if "```" in edited_code: | |
| # コードブロックのマークアップを削除 | |
| code_pattern = r"```(?:\w+)?\n([\s\S]*?)\n```" | |
| match = re.search(code_pattern, edited_code) | |
| if match: | |
| edited_code = match.group(1) | |
| return edited_code | |
| except Exception as e: | |
| st.error(f"AIによるコード編集中にエラーが発生しました: {str(e)}") | |
| return None | |
| def update_conversation_state(user_input, ai_response): | |
| """会話の状態を更新する""" | |
| # 主要トピックの更新(最初のメッセージから) | |
| if st.session_state.conversation_state["topic"] is None and user_input: | |
| # 簡易的なトピック抽出(最初の文か最初の30単語) | |
| first_sentence = user_input.split('.')[0] | |
| topic = first_sentence[:100] + "..." if len(first_sentence) > 100 else first_sentence | |
| st.session_state.conversation_state["topic"] = topic | |
| # フォローアップカウントの更新 | |
| if "?" in user_input or "?" in user_input: | |
| st.session_state.conversation_state["follow_up_count"] += 1 | |
| def summarize_chat(): | |
| """チャット履歴を要約する""" | |
| # APIキーの取得 | |
| api_key = load_api_key() | |
| if not api_key: | |
| return "APIキーが設定されていないため、要約を生成できません。" | |
| client = Groq(api_key=api_key) | |
| # チャット履歴を文字列にまとめる | |
| chat_history = "" | |
| for msg in st.session_state.messages: | |
| role = "ユーザー" if msg["role"] == "user" else "アシスタント" | |
| chat_history += f"{role}: {msg['content']}\n\n" | |
| # 要約を生成 | |
| from config import MODELS | |
| summarize_prompt = f""" | |
| 以下はユーザーとアシスタントの間の会話です。この会話を300単語以内で要約してください。 | |
| 特に重要な内容、技術的な詳細、解決した問題に焦点を当ててください。 | |
| {chat_history} | |
| """ | |
| # Mistral Sabaモデルのシステムプロンプトをロード | |
| summary_system_prompt = load_system_prompt(MODELS["mistral_saba"]) | |
| summary_response = client.chat.completions.create( | |
| model=MODELS["mistral_saba"], | |
| messages=[ | |
| {"role": "system", "content": summary_system_prompt}, | |
| {"role": "user", "content": summarize_prompt} | |
| ], | |
| temperature=0.3, | |
| max_tokens=1000 | |
| ) | |
| summary = summary_response.choices[0].message.content | |
| st.session_state.chat_summary = summary | |
| # 要約をthinking_processに追加 | |
| st.session_state.thinking_process.append({ | |
| "title": "前回の会話要約", | |
| "content": summary | |
| }) | |
| return summary |