import streamlit as st import os # 使用可能なモデルのリスト MODELS = { "mistral_saba": "qwen-qwq-32b", "qwen_coder": "qwen-2.5-coder-32b", "deepseek": "deepseek-r1-distill-llama-70b" } # 動作モードの定義 MODES = ["通常モード", "実装モード", "エラー修正モード"] # モード説明の表示 MODE_DESCRIPTIONS = { "通常モード": "Mistral Sabaがユーザー入力を分析し、必要に応じてDeepSeekの知識も活用した上で、Qwen Coderが回答を生成します。", "実装モード": "ユーザー入力の分析に基づき、Qwen Coderが設計書を作成した後、実装コードを提供します。", "エラー修正モード": "提示されたコードのエラーを分析し、Qwen Coderが修正案を提供します。" } # サイドバーの設定UI def setup_config(compact=False): """サイドバーの設定UIをセットアップする""" st.title("コーディングアシスタント 💻" if not compact else "設定 ⚙️") # モード選択タブ selected_mode = st.radio( "モード選択", MODES, key="mode_selection" ) # 選択されたモードの説明 if not compact: st.markdown(f"**{selected_mode}**: {MODE_DESCRIPTIONS[selected_mode]}") # モード変更の検出 if st.session_state.current_mode != selected_mode: st.session_state.current_mode = selected_mode st.session_state.is_first_message = True # モード変更時にこれまでのチャットを要約 if not compact and len(st.session_state.messages) > 0: with st.spinner("前回の会話内容を要約しています..."): from chat import summarize_chat try: summary = summarize_chat() st.info(f"モードが変更されました。前回の会話の要約:\n\n{summary}") except Exception as e: st.error(f"会話要約の生成中にエラーが発生しました: {str(e)}") # ファイル管理セクション if not compact: show_file_management() else: if st.button("ファイル管理", use_container_width=True): st.session_state.show_file_dialog = True # ファイル管理ダイアログ(コンパクトモードの場合) if compact and st.session_state.get("show_file_dialog", False): with st.expander("ファイル管理", expanded=True): show_file_management() if st.button("閉じる"): st.session_state.show_file_dialog = False st.rerun() # AI編集モード if not compact: st.checkbox("AIに編集を任せる", value=st.session_state.ai_edit_mode, key="ai_edit_mode_toggle") if st.session_state.ai_edit_mode: st.session_state.edit_instructions = st.text_area( "AI編集の指示", value=st.session_state.edit_instructions, placeholder="例: インデントを修正して、コメントを追加してください", height=100 ) # チャット履歴のクリア機能 if st.button("会話履歴をクリア", use_container_width=True): st.session_state.messages = [] st.session_state.thinking_process = [] st.session_state.is_first_message = True st.session_state.conversation_state = { "topic": None, "last_question_type": None, "mentioned_entities": set(), "follow_up_count": 0 } st.rerun() # 使い方ガイド(非コンパクトモードのみ) if not compact: show_help_guides() def show_file_management(): """ファイル管理UIを表示する""" st.header("ファイル管理") # ファイルアップロード機能を追加 st.subheader("ファイルアップロード") uploaded_files = st.file_uploader("コードファイルをアップロード", accept_multiple_files=True, type=["py", "js", "html", "css", "json", "txt"]) if uploaded_files: for uploaded_file in uploaded_files: file_content = uploaded_file.getvalue().decode("utf-8") if uploaded_file.name not in st.session_state.files: st.session_state.files[uploaded_file.name] = file_content st.success(f"'{uploaded_file.name}'をアップロードしました") # 新規ファイル作成 st.subheader("新規ファイル作成") new_file_name = st.text_input("新規ファイル名", value="", key="new_file") new_file_col1, new_file_col2 = st.columns([3, 1]) with new_file_col1: file_extension = st.selectbox( "拡張子", options=[".py", ".js", ".html", ".css", ".json", ".txt"] ) with new_file_col2: if st.button("作成", key="create_file_btn", use_container_width=True): full_filename = new_file_name + file_extension if full_filename not in st.session_state.files and new_file_name: st.session_state.files[full_filename] = "" st.session_state.current_file = full_filename st.success(f"ファイル '{full_filename}' を作成しました") st.rerun() elif not new_file_name: st.error("ファイル名を入力してください") else: st.error(f"ファイル '{full_filename}' は既に存在します") # 既存ファイル一覧 if st.session_state.files: st.subheader("既存ファイル") for filename in st.session_state.files.keys(): col1, col2, col3 = st.columns([3, 1, 1]) with col1: if st.button(filename, key=f"open_{filename}"): st.session_state.current_file = filename st.rerun() with col2: if st.button("削除", key=f"delete_{filename}"): del st.session_state.files[filename] if st.session_state.current_file == filename: st.session_state.current_file = None st.rerun() with col3: if st.button("コピー", key=f"copy_{filename}"): name, ext = os.path.splitext(filename) new_name = f"{name}_copy{ext}" counter = 1 while new_name in st.session_state.files: new_name = f"{name}_copy_{counter}{ext}" counter += 1 st.session_state.files[new_name] = st.session_state.files[filename] st.rerun() def show_help_guides(): """使い方ガイドを表示する""" st.markdown("---") st.subheader("使い方ガイド") with st.expander("アプリの機能", expanded=False): st.markdown(""" ### 主な機能 1. **コードエディタ** - 複数のプログラミング言語に対応したシンタックスハイライト付きエディタ 2. **Pythonコード実行** - エディタ上のPythonコードをその場で実行 3. **AIアシスタント** - コーディングに関する質問や実装の提案 4. **AIコード編集** - AIにコードの編集を依頼 5. **ファイル管理** - 複数のファイルの作成・保存・編集 6. **エラー修正** - コードのエラーを自動で診断・修正 7. **複数ファイルの解析** - アップロードされた複数ファイルを解析 ### 特殊コマンド - `/mode 通常` - 通常の質問応答モードに切り替え - `/mode 実装` - コード実装モードに切り替え - `/mode エラー修正` - エラー修正モードに切り替え - `/edit` - 現在のコードをAIに編集させる - `/analyse` - アップロードされたファイル群を解析 """) with st.expander("モバイル利用のコツ", expanded=False): st.markdown(""" ### モバイルでの使用Tips 1. **タブ切り替え** - エディタとチャットを切り替えて使用します 2. **横向き表示** - 横向きにすると画面が広く使えます 3. **AIに編集を任せる** - コード入力が難しい場合はAIに編集を依頼できます 4. **短いコード** - モバイルでは短く分割したコードが扱いやすいです 5. **コード保存** - 作業中は定期的にコードを保存しましょう """) with st.expander("複数ファイル解析", expanded=False): st.markdown(""" ### 複数ファイル解析の使い方 1. **ファイルのアップロード** - 「ファイルアップロード」セクションから複数のファイルをアップロードします 2. **解析コマンド** - チャットで `/analyse` を入力すると、アップロードされたファイル群の関係性を解析します 3. **ファイル指定** - 特定のファイルについて質問する場合は、質問文中にファイル名を含めてください 4. **コード理解** - 複数のファイルにまたがるコードの流れや依存関係も分析できます """) def load_api_key(): """APIキーを環境変数またはSecretsから取得する""" # 環境変数から取得 api_key = os.getenv("GROQ_API_KEY", "") # secrets.tomlから取得(環境変数がない場合) if not api_key and hasattr(st, 'secrets') and 'GROQ_API_KEY' in st.secrets: api_key = st.secrets['GROQ_API_KEY'] return api_key