|
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() |
|
|
|
st.title("🔍 リポジトリ解析・質問システム") |
|
|
|
with st.sidebar: |
|
if not st.session_state.llm_service.settings.anthropic_api_key: |
|
st.error("Anthropic API key is required") |
|
st.stop() |
|
|
|
st.write("Using Claude model") |
|
|
|
|
|
use_llm = st.toggle("LLMによるコード解説を有効にする", value=True, key="use_llm") |
|
|
|
st.divider() |
|
st.subheader("📌 使い方") |
|
if use_llm: |
|
st.markdown(""" |
|
1. GitHubリポジトリのURLを入力 |
|
2. スキャンを実行 |
|
3. コードについて質問(最大5ターンの会話が可能) |
|
""") |
|
else: |
|
st.markdown(""" |
|
1. GitHubリポジトリのURLを入力 |
|
2. スキャンを実行してコードを解析 |
|
""") |
|
|
|
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 and st.session_state.use_llm: |
|
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 |