DeL-TaiseiOzaki commited on
Commit
5bdd26d
·
verified ·
1 Parent(s): f81a94c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -159
app.py CHANGED
@@ -1,177 +1,114 @@
 
 
1
  import streamlit as st
2
- import tempfile
3
- import git
4
- import os
5
  from pathlib import Path
6
- from datetime import datetime
7
- from typing import List, Set, Optional
8
- from config.settings import Settings
9
  from core.file_scanner import FileScanner, FileInfo
10
- from services.llm_service import LLMService
11
- from main import DirectoryStructureScanner, MarkdownGenerator
12
 
13
- class StreamlitFileWriter:
14
- def __init__(self, output_file: Path):
15
- self.output_file = output_file
16
-
17
- def create_markdown(self, files: List[FileInfo]) -> str:
18
- content = []
19
- for file_info in files:
20
- content.append(f"## {file_info.path}")
21
- content.append("------------")
22
- if file_info.content is not None:
23
- content.append(file_info.content)
24
- else:
25
- content.append("# Failed to read content")
26
- content.append("\n")
27
- return "\n".join(content)
28
 
29
- # ページ設定
30
- st.set_page_config(
31
- page_title="Repository Code Analysis",
32
- page_icon="🔍",
33
- layout="wide"
34
- )
35
 
36
- # スタイル設定
37
- st.markdown("""
38
- <style>
39
- .stApp {
40
- background-color: #0e1117;
41
- color: #ffffff;
42
- }
43
- .directory-structure {
44
- font-family: monospace;
45
- white-space: pre;
46
- background-color: #1e2329;
47
- padding: 1rem;
48
- border-radius: 0.5rem;
49
- margin: 1rem 0;
50
- }
51
- .download-section {
52
- background-color: #1e2329;
53
- padding: 1rem;
54
- border-radius: 0.5rem;
55
- margin: 1rem 0;
56
- }
57
- </style>
58
- """, unsafe_allow_html=True)
59
 
60
- # セッション状態の初期化
61
- if 'repo_content' not in st.session_state:
62
- st.session_state.repo_content = None
63
- if 'structure_md' not in st.session_state:
64
- st.session_state.structure_md = None
65
- if 'content_md' not in st.session_state:
66
- st.session_state.content_md = None
67
- if 'temp_dir' not in st.session_state:
68
- st.session_state.temp_dir = None
69
- if 'llm_service' not in st.session_state:
70
- try:
71
- api_key = os.getenv("ANTHROPIC_API_KEY")
72
- if not api_key:
73
- st.error("ANTHROPIC_API_KEY環境変数が設定されていません")
74
- st.stop()
75
- st.session_state.llm_service = LLMService(api_key)
76
- except Exception as e:
77
- st.error(str(e))
78
- st.stop()
79
 
80
- # メインのUIレイアウト
81
- st.title("🔍 リポジトリ解析システム")
 
 
 
 
 
 
82
 
83
- # サイドバーの設定
84
- with st.sidebar:
85
- st.subheader("📌 使い方")
86
- st.markdown("""
87
- 1. GitHubリポジトリのURLを入力
88
- 2. スキャン対象の拡張子を選択
89
- 3. スキャンを実行
90
- 4. ディレクトリ構造を確認
91
- 5. 解析結果をダウンロード
92
- """)
93
-
94
- # スキャン対象の拡張子選択
95
- st.subheader("🔍 スキャン対象の選択")
96
-
97
- # プログラミング言語
98
- st.write("プログラミング言語:")
99
- prog_exts = {'.py', '.js', '.ts', '.java', '.cpp', '.hpp', '.c', '.h', '.go', '.rs'}
100
- selected_prog = {ext: st.checkbox(ext, value=True, key=f"prog_{ext}")
101
- for ext in prog_exts}
102
-
103
- # 設定ファイル
104
- st.write("設定ファイル:")
105
- config_exts = {'.json', '.yml', '.yaml', '.toml'}
106
- selected_config = {ext: st.checkbox(ext, value=True, key=f"config_{ext}")
107
- for ext in config_exts}
108
-
109
- # ドキュメント
110
- st.write("ドキュメント:")
111
- doc_exts = {'.md', '.txt'}
112
- selected_doc = {ext: st.checkbox(ext, value=True, key=f"doc_{ext}")
113
- for ext in doc_exts}
114
 
115
- # URLの入力
116
- repo_url = st.text_input(
117
- "GitHubリポジトリのURLを入力",
118
- placeholder="https://github.com/username/repository.git"
119
- )
120
 
121
- # スキャン実行ボタン
122
- if st.button("スキャン開始", disabled=not repo_url):
123
- try:
124
- with st.spinner('リポジトリを解析中...'):
125
- # 選択された拡張子を集約
126
- selected_extensions = {ext for exts in [selected_prog, selected_config, selected_doc]
127
- for ext, selected in exts.items() if selected}
128
-
129
- # 選択がない場合はデフォルトを使用
130
- if not selected_extensions:
131
- selected_extensions = Settings.DEFAULT_EXTENSIONS
132
-
133
- # MarkdownGeneratorを初期化
134
- generator = MarkdownGenerator(repo_url, selected_extensions)
135
- content_md, structure_md, files = generator.generate_markdowns()
136
-
137
- writer = StreamlitFileWriter(Path("dummy"))
138
- formatted_content = writer.create_markdown(files)
139
-
140
- st.session_state.content_md = formatted_content
141
- st.session_state.structure_md = structure_md
142
- st.session_state.repo_content = LLMService.format_code_content(files)
143
-
144
- st.success(f"スキャン完了: {len(files)}個のファイルを検出")
145
-
146
- except Exception as e:
147
- st.error(f"エラーが発生しました: {str(e)}")
148
 
149
- # スキャン結果の表示とダウンロード
150
- if st.session_state.structure_md and st.session_state.content_md:
151
- # ディレクトリ構造の表示
152
- st.subheader("📁 ディレクトリ構造")
153
- st.markdown(f'<div class="directory-structure">{st.session_state.structure_md}</div>',
154
- unsafe_allow_html=True)
155
-
156
- # ダウンロードセクション
157
- st.subheader("📥 解析結果のダウンロード")
158
- st.markdown('<div class="download-section">', unsafe_allow_html=True)
159
-
160
  col1, col2 = st.columns(2)
161
- with col1:
162
- st.download_button(
163
- label="📁 ディレクトリ構造をダウンロード",
164
- data=st.session_state.structure_md,
165
- file_name=f"directory_structure_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
166
- mime="text/markdown"
167
- )
168
 
 
 
 
 
 
 
 
 
169
  with col2:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  st.download_button(
171
- label="📄 ファイル内容をダウンロード",
172
- data=st.session_state.content_md,
173
- file_name=f"repository_content_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
174
  mime="text/markdown"
175
  )
176
-
177
- st.markdown('</div>', unsafe_allow_html=True)
 
1
+ # app.py
2
+
3
  import streamlit as st
 
 
 
4
  from pathlib import Path
 
 
 
5
  from core.file_scanner import FileScanner, FileInfo
 
 
6
 
7
+ # セッション状態を初期化
8
+ if 'scanned_files' not in st.session_state:
9
+ st.session_state.scanned_files = [] # List[FileInfo]
10
+ if 'selected_files' not in st.session_state:
11
+ st.session_state.selected_files = set() # {Path} 相対パス or 絶対パス
 
 
 
 
 
 
 
 
 
 
12
 
13
+ st.title("指定拡張子のみディレクトリスキャン & 選択ダウンロード")
 
 
 
 
 
14
 
15
+ # --- 1) ユーザー入力: スキャンしたいローカルディレクトリパス ---
16
+ dir_path_str = st.text_input("ローカルディレクトリパス", value=".")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ # --- 2) ユーザー入力: スキャン対象拡張子選択 ---
19
+ st.subheader("スキャン対象拡張子")
20
+ available_exts = [".py", ".js", ".ts", ".md", ".txt", ".java", ".cpp"]
21
+ chosen_exts = []
22
+ for ext in available_exts:
23
+ if st.checkbox(ext, key=f"ext_{ext}", value=(ext in [".py", ".md"])):
24
+ chosen_exts.append(ext)
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ # --- 3) 「スキャン実行」ボタン ---
27
+ if st.button("スキャン開始"):
28
+ base_path = Path(dir_path_str).resolve()
29
+ if not base_path.is_dir():
30
+ st.error("有効なディレクトリを指定してください")
31
+ else:
32
+ scanner = FileScanner(base_path, set(chosen_exts))
33
+ found_files = scanner.scan_files()
34
 
35
+ st.session_state.scanned_files = found_files
36
+ st.session_state.selected_files = set() # スキャン毎に選択リセット
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ st.success(f"スキャン完了: {len(found_files)}個のファイル")
 
 
 
 
39
 
40
+ # --- 4) ファイルを表示するUI(ディレクトリ構造 or リスト) + 全選択 / 全解除 ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ if st.session_state.scanned_files:
43
+ # 全選択 / 全解除ボタン
 
 
 
 
 
 
 
 
 
44
  col1, col2 = st.columns(2)
 
 
 
 
 
 
 
45
 
46
+ with col1:
47
+ if st.button("すべて選択"):
48
+ # すべてのファイルを選択
49
+ base_path = Path(dir_path_str).resolve()
50
+ # relative_to で相対パスを使うか、絶対パスを使うかは好みでOK
51
+ st.session_state.selected_files = {
52
+ f.path.relative_to(base_path) for f in st.session_state.scanned_files
53
+ }
54
  with col2:
55
+ if st.button("すべて解除"):
56
+ st.session_state.selected_files = set()
57
+
58
+ st.write("### ファイル一覧 (指定拡張子のみ)")
59
+ base_path = Path(dir_path_str).resolve()
60
+
61
+ for file_info in st.session_state.scanned_files:
62
+ # 相対パスで扱う例
63
+ rel_path = file_info.path.relative_to(base_path)
64
+ checked = (rel_path in st.session_state.selected_files)
65
+
66
+ # チェックボックス + ファイル名表示
67
+ new_checked = st.checkbox(
68
+ f"{rel_path} ({file_info.formatted_size})",
69
+ value=checked,
70
+ key=str(rel_path) # keyは重複しないように文字列にしておく
71
+ )
72
+
73
+ # 選択状態を更新
74
+ if new_checked:
75
+ st.session_state.selected_files.add(rel_path)
76
+ else:
77
+ st.session_state.selected_files.discard(rel_path)
78
+
79
+ # --- 5) 選択ファイルをまとめてMarkdown化 & ダウンロード ---
80
+
81
+ def create_markdown_for_selected(files, selected_paths, base_dir: Path) -> str:
82
+ """
83
+ 選択されたファイルだけをMarkdownテキストとして返す
84
+ """
85
+ output = []
86
+ for f in files:
87
+ rel_path = f.path.relative_to(base_dir)
88
+ if rel_path in selected_paths:
89
+ output.append(f"## {rel_path}")
90
+ output.append("------------")
91
+ if f.content is not None:
92
+ output.append(f.content)
93
+ else:
94
+ output.append("# Failed to read content")
95
+ output.append("") # 空行
96
+
97
+ return "\n".join(output)
98
+
99
+ if st.session_state.scanned_files:
100
+ st.write("### 選択ファイルをMarkdownダウンロード")
101
+ if st.button("選択ファイルをまとめてダウンロード"):
102
+ base_path = Path(dir_path_str).resolve()
103
+ markdown_text = create_markdown_for_selected(
104
+ st.session_state.scanned_files,
105
+ st.session_state.selected_files,
106
+ base_path
107
+ )
108
+
109
  st.download_button(
110
+ label="ダウンロード",
111
+ data=markdown_text,
112
+ file_name="selected_files.md",
113
  mime="text/markdown"
114
  )