Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import tempfile | |
| import subprocess | |
| import os | |
| from streamlit_ace import st_ace | |
| from utils import save_generated_code_to_new_file # chatではなくutilsからインポート | |
| def setup_editor(api_key, mobile=False): | |
| """コードエディタとその関連機能をセットアップする""" | |
| # ヘッダー | |
| st.header("コードエディタ" if not mobile else "エディタ") | |
| if 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": "text" | |
| } | |
| language = lang_map.get(file_ext, "python") | |
| # エディタの表示 | |
| st.subheader(f"編集中: {st.session_state.current_file}") | |
| # モバイル向けの調整 | |
| editor_height = 300 if mobile else 500 | |
| # AIに編集を任せるモード | |
| if st.session_state.ai_edit_mode and st.button("AIに編集を依頼", key="ai_edit_btn"): | |
| current_code = st.session_state.files[st.session_state.current_file] | |
| instructions = st.session_state.edit_instructions or "コードを改善してください" | |
| with st.spinner("AIによるコード編集中..."): | |
| # request_ai_editを直接インポートせず、関数を通して呼び出す | |
| from chat import request_ai_edit # 関数を使う直前でインポート(循環を避ける) | |
| 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 | |
| st.success("AIによるコード編集が完了しました") | |
| st.session_state.active_tab = "エディタ" # モバイルの場合はエディタタブに戻る | |
| st.rerun() | |
| # エディタを表示 | |
| code = st_ace( | |
| value=st.session_state.files[st.session_state.current_file], | |
| language=language, | |
| theme="monokai", | |
| keybinding="vscode", | |
| font_size=14 if not mobile else 16, # モバイルでは少し大きく | |
| min_lines=10, | |
| max_lines=50, | |
| height=editor_height, | |
| key=f"ace_editor_{st.session_state.current_file}" | |
| ) | |
| # コードを保存 | |
| st.session_state.files[st.session_state.current_file] = code | |
| # アクション関連のボタン | |
| col1, col2 = st.columns(2) | |
| # Python codeの場合、実行ボタンを表示 | |
| if language == "python": | |
| with col1: | |
| if st.button("コードを実行", key="run_code_btn", use_container_width=True): | |
| run_python_code(code) | |
| # ファイルダウンロード機能 | |
| with col2: | |
| st.download_button( | |
| label="ファイルをダウンロード", | |
| data=code, | |
| file_name=st.session_state.current_file, | |
| key="download_file", | |
| use_container_width=True | |
| ) | |
| else: | |
| st.info("サイドバーから既存のファイルを選択するか、新しいファイルを作成してください。") | |
| # 簡易ファイル作成ボタン(特にモバイル向け) | |
| if st.button("新しいPythonファイルを作成", key="quick_file_create", use_container_width=True): | |
| import time | |
| filename = f"script_{int(time.time())}.py" | |
| st.session_state.files[filename] = "# 新しいPythonスクリプト\n\n# コードを入力してください\n" | |
| st.session_state.current_file = filename | |
| st.rerun() | |
| def run_python_code(code): | |
| """Pythonコードを実行して結果を表示する""" | |
| with st.spinner("コードを実行中..."): | |
| # 一時ファイルを作成してコードを実行 | |
| with tempfile.NamedTemporaryFile(suffix='.py', delete=False) as tmp: | |
| tmp.write(code.encode()) | |
| tmp_name = tmp.name | |
| try: | |
| # サブプロセスでPythonコードを実行 | |
| result = subprocess.run( | |
| ["python", tmp_name], | |
| capture_output=True, | |
| text=True, | |
| timeout=10 # 10秒のタイムアウト | |
| ) | |
| # 実行結果を表示 | |
| if result.stdout: | |
| st.code(result.stdout, language="text") | |
| if result.stderr: | |
| st.error(result.stderr) | |
| # エラーが発生した場合、自動的にエラー修正モードに切り替える | |
| if st.button("エラー修正AIに相談", key="ask_error_fix"): | |
| st.session_state.current_mode = "エラー修正モード" | |
| error_query = f"次のPythonコードにエラーがあります。修正方法を教えてください。\n\n```python\n{code}\n```\n\nエラーメッセージ:\n```\n{result.stderr}\n```" | |
| # チャットにエラー修正用のメッセージを追加 | |
| if "messages" not in st.session_state: | |
| st.session_state.messages = [] | |
| st.session_state.messages.append({"role": "user", "content": error_query}) | |
| st.session_state.active_tab = "チャット" # モバイルモードではチャットタブに切り替え | |
| st.rerun() | |
| except subprocess.TimeoutExpired: | |
| st.error("実行がタイムアウトしました(10秒以上かかりました)") | |
| except Exception as e: | |
| st.error(f"実行中にエラーが発生しました: {str(e)}") | |
| finally: | |
| # 一時ファイルを削除 | |
| if os.path.exists(tmp_name): | |
| os.unlink(tmp_name) | |
| def save_generated_code_to_new_file(code_text, suggested_name=None): | |
| """AIが生成したコードを新しいファイルに保存する""" | |
| import re | |
| # コードブロックのマークアップを削除 | |
| code_pattern = r"```(?:\w+)?\n([\s\S]*?)\n```" | |
| match = re.search(code_pattern, code_text) | |
| clean_code = match.group(1) if match else code_text | |
| # 言語の推測 | |
| language = "py" # デフォルトはPython | |
| lang_pattern = r"```(\w+)\n" | |
| lang_match = re.search(lang_pattern, code_text) | |
| if lang_match: | |
| detected_lang = lang_match.group(1).lower() | |
| lang_mapping = { | |
| "python": "py", | |
| "javascript": "js", | |
| "html": "html", | |
| "css": "css", | |
| "json": "json", | |
| "java": "java", | |
| "cpp": "cpp", | |
| "c++": "cpp", | |
| "c": "c", | |
| "go": "go", | |
| "rust": "rs", | |
| "typescript": "ts" | |
| } | |
| language = lang_mapping.get(detected_lang, detected_lang) | |
| # ファイル名の生成 | |
| if not suggested_name: | |
| import hashlib | |
| import time | |
| # タイムスタンプとコードの一部からハッシュを生成 | |
| timestamp = str(int(time.time())) | |
| code_hash = hashlib.md5(clean_code[:100].encode()).hexdigest()[:6] | |
| suggested_name = f"generated_{timestamp}_{code_hash}" | |
| # 拡張子が含まれていなければ追加 | |
| if "." not in suggested_name: | |
| suggested_name = f"{suggested_name}.{language}" | |
| # 同名ファイルが存在する場合は連番を付ける | |
| base_name, ext = os.path.splitext(suggested_name) | |
| counter = 1 | |
| file_name = suggested_name | |
| while file_name in st.session_state.files: | |
| file_name = f"{base_name}_{counter}{ext}" | |
| counter += 1 | |
| # ファイルを保存 | |
| st.session_state.files[file_name] = clean_code | |
| st.session_state.current_file = file_name | |
| return file_name |