DeL-TaiseiOzaki
S
0520d98
raw
history blame
5.76 kB
import streamlit as st
import tempfile
import git
from pathlib import Path
from datetime import datetime
from services.llm_service import LLMService
from core.file_scanner import FileScanner, FileInfo
from typing import List
# ページ設定
st.set_page_config(
page_title="Repository Code Analysis",
page_icon="🔍",
layout="wide"
)
# ダークテーマの設定
st.markdown("""
<style>
.stApp {
background-color: #0e1117;
color: #ffffff;
}
.chat-message {
padding: 1rem;
margin: 1rem 0;
border-radius: 0.5rem;
}
.assistant-message {
background-color: #1e2329;
color: #ffffff;
}
.stButton button {
background-color: #2ea44f;
color: #ffffff;
}
.stTextArea textarea {
background-color: #1e2329;
color: #ffffff;
}
</style>
""", unsafe_allow_html=True)
def create_download_content(files: List[FileInfo]) -> str:
content = "# スキャン結果\n\n"
for file in files:
content += f"## {file.path}\n"
content += f"サイズ: {file.formatted_size}\n"
content += f"エンコーディング: {file.encoding or '不明'}\n\n"
if file.content:
content += f"```{file.extension[1:] if file.extension else ''}\n"
content += file.content
content += "\n```\n\n"
return content
def clone_repository(repo_url: str) -> Path:
"""リポジトリをクローンして一時ディレクトリに保存"""
temp_dir = Path(tempfile.mkdtemp())
git.Repo.clone_from(repo_url, temp_dir)
return temp_dir
# セッション状態の初期化
if 'repo_content' not in st.session_state:
st.session_state.repo_content = None
if 'temp_dir' not in st.session_state:
st.session_state.temp_dir = None
if 'llm_service' not in st.session_state:
try:
st.session_state.llm_service = LLMService()
except ValueError as e:
st.error(str(e))
st.stop()
# メインのUIレイアウト
st.title("🔍 リポジトリ解析・質問システム")
# サイドバーでモデル選択
with st.sidebar:
available_models = []
if st.session_state.llm_service.settings.anthropic_api_key:
available_models.append("Claude")
if st.session_state.llm_service.settings.openai_api_key:
available_models.append("OpenAI")
if available_models:
model = st.radio(
"使用するモデル",
available_models,
key="model_selection"
)
st.session_state.llm_service.switch_model(model.lower())
st.divider()
st.subheader("📌 使い方")
st.markdown("""
1. GitHubリポジトリのURLを入力
2. スキャンを実行
3. コードについて質問(最大5ターンの会話が可能)
""")
st.subheader("🔍 スキャン対象")
st.markdown("""
- Python (.py)
- JavaScript (.js)
- Java (.java)
- C/C++ (.c, .h, .cpp, .hpp)
- その他の主要なプログラミング言語
""")
# URLの入力
repo_url = st.text_input(
"GitHubリポジトリのURLを入力",
placeholder="https://github.com/username/repository.git"
)
# スキャン実行ボタン
if st.button("スキャン開始", disabled=not repo_url):
try:
with st.spinner('リポジトリをクローン中...'):
temp_dir = clone_repository(repo_url)
st.session_state.temp_dir = temp_dir
with st.spinner('ファイルをスキャン中...'):
scanner = FileScanner(temp_dir)
files = scanner.scan_files()
st.session_state.repo_content = LLMService.format_code_content(files)
st.success(f"スキャン完了: {len(files)}個のファイルを検出")
# スキャン結果のダウンロードボタン
scan_result = create_download_content(files)
st.download_button(
label="スキャン結果をダウンロード",
data=scan_result,
file_name=f"scan_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
mime="text/markdown"
)
# 新しいスキャン時に会話履歴をクリア
st.session_state.llm_service.clear_history()
except Exception as e:
st.error(f"エラーが発生しました: {str(e)}")
# スキャン完了後の質問セクション
if st.session_state.repo_content:
st.divider()
st.subheader("💭 コードについて質問する")
# 会話履歴の表示(アシスタントの回答のみ)
for message in st.session_state.llm_service.conversation_history:
if message.role == "assistant":
st.markdown(f'<div class="chat-message assistant-message">{message.content}</div>',
unsafe_allow_html=True)
query = st.text_area(
"質問を入力してください",
placeholder="例: このコードの主な機能は何ですか?"
)
col1, col2 = st.columns([1, 5])
with col1:
if st.button("履歴クリア"):
st.session_state.llm_service.clear_history()
st.rerun()
with col2:
if st.button("質問する", disabled=not query):
with st.spinner('回答を生成中...'):
response, error = st.session_state.llm_service.get_response(
st.session_state.repo_content,
query
)
if error:
st.error(error)
else:
st.rerun()
# セッション終了時のクリーンアップ
if st.session_state.temp_dir and Path(st.session_state.temp_dir).exists():
try:
import shutil
shutil.rmtree(st.session_state.temp_dir)
except:
pass