Groxy2 / editor.py
Yasu777's picture
Update editor.py
530ea3b verified
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