DeL-TaiseiOzaki commited on
Commit
b212889
·
1 Parent(s): ed30199
Files changed (5) hide show
  1. app.py +110 -93
  2. config/settings.py +29 -2
  3. core/file_scanner.py +37 -22
  4. scan.sh +4 -45
  5. services/llm_service.py +14 -31
app.py CHANGED
@@ -1,11 +1,13 @@
1
  import streamlit as st
2
  import tempfile
3
  import git
 
4
  from pathlib import Path
5
  from datetime import datetime
6
- from services.llm_service import LLMService
7
  from core.file_scanner import FileScanner, FileInfo
8
- from typing import List
 
9
 
10
  st.set_page_config(
11
  page_title="Repository Code Analysis",
@@ -13,121 +15,135 @@ st.set_page_config(
13
  layout="wide"
14
  )
15
 
 
16
  st.markdown("""
17
  <style>
18
- .stApp {
19
- background-color: #0e1117;
20
- color: #ffffff;
21
- }
22
- .chat-message {
23
- padding: 1rem;
24
- margin: 1rem 0;
25
- border-radius: 0.5rem;
26
- }
27
- .assistant-message {
28
- background-color: #1e2329;
29
- color: #ffffff;
30
- }
31
- .stButton button {
32
- background-color: #2ea44f;
33
- color: #ffffff;
34
- }
35
- .stTextArea textarea {
36
- background-color: #1e2329;
37
- color: #ffffff;
38
- }
39
  </style>
40
  """, unsafe_allow_html=True)
41
 
42
  def create_download_content(files: List[FileInfo]) -> str:
43
- content = "# スキャン結果\n\n"
44
- for file in files:
45
- content += f"## {file.path}\n"
46
- content += f"サイズ: {file.formatted_size}\n"
47
- content += f"エンコーディング: {file.encoding or '不明'}\n\n"
48
- if file.content:
49
- content += f"```{file.extension[1:] if file.extension else ''}\n"
50
- content += file.content
51
- content += "\n```\n\n"
52
- return content
53
 
54
  def clone_repository(repo_url: str) -> Path:
55
- temp_dir = Path(tempfile.mkdtemp())
56
- git.Repo.clone_from(repo_url, temp_dir)
57
- return temp_dir
58
 
 
59
  if 'repo_content' not in st.session_state:
60
- st.session_state.repo_content = None
61
  if 'temp_dir' not in st.session_state:
62
- st.session_state.temp_dir = None
63
  if 'llm_service' not in st.session_state:
64
- try:
65
- st.session_state.llm_service = LLMService()
66
- except ValueError as e:
67
- st.error(str(e))
68
- st.stop()
 
 
 
 
69
 
 
70
  st.title("🔍 リポジトリ解析・質問システム")
71
 
 
72
  with st.sidebar:
73
- if not st.session_state.llm_service.settings.anthropic_api_key:
74
- st.error("Anthropic API key is required")
75
- st.stop()
 
 
 
 
76
 
77
- st.write("Using Claude model")
 
78
 
79
- # LLM機能の切り替え
80
- use_llm = st.toggle("LLMによるコード解説を有効にする", value=True, key="use_llm")
81
-
82
- st.divider()
83
- st.subheader("📌 使い方")
84
- if use_llm:
85
- st.markdown("""
86
- 1. GitHubリポジトリのURLを入力
87
- 2. スキャンを実行
88
- 3. コードについて質問(最大5ターンの会話が可能)
89
- """)
90
- else:
91
- st.markdown("""
92
- 1. GitHubリポジトリのURLを入力
93
- 2. スキャンを実行してコードを解析
94
- """)
 
 
 
95
 
 
96
  repo_url = st.text_input(
97
  "GitHubリポジトリのURLを入力",
98
  placeholder="https://github.com/username/repository.git"
99
  )
100
 
 
101
  if st.button("スキャン開始", disabled=not repo_url):
102
- try:
103
- with st.spinner('リポジトリをクローン中...'):
104
- temp_dir = clone_repository(repo_url)
105
- st.session_state.temp_dir = temp_dir
106
-
107
- with st.spinner('ファイルをスキャン中...'):
108
- scanner = FileScanner(temp_dir)
109
- files = scanner.scan_files()
110
- st.session_state.repo_content = LLMService.format_code_content(files)
111
-
112
- st.success(f"スキャン完了: {len(files)}個のファイルを検出")
113
-
114
- scan_result = create_download_content(files)
115
- st.download_button(
116
- label="スキャン結果をダウンロード",
117
- data=scan_result,
118
- file_name=f"scan_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
119
- mime="text/markdown"
120
- )
121
-
122
- st.session_state.llm_service.clear_history()
123
-
124
- except Exception as e:
125
- st.error(f"エラーが発生しました: {str(e)}")
 
 
126
 
127
- if st.session_state.repo_content and st.session_state.use_llm:
 
128
  st.divider()
129
  st.subheader("💭 コードについて質問する")
130
 
 
131
  for message in st.session_state.llm_service.conversation_history:
132
  if message.role == "assistant":
133
  st.markdown(f'<div class="chat-message assistant-message">{message.content}</div>',
@@ -157,9 +173,10 @@ if st.session_state.repo_content and st.session_state.use_llm:
157
  else:
158
  st.rerun()
159
 
 
160
  if st.session_state.temp_dir and Path(st.session_state.temp_dir).exists():
161
- try:
162
- import shutil
163
- shutil.rmtree(st.session_state.temp_dir)
164
- except:
165
- pass
 
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 config.settings import Settings
8
  from core.file_scanner import FileScanner, FileInfo
9
+ from services.llm_service import LLMService
10
+ from typing import List, Set
11
 
12
  st.set_page_config(
13
  page_title="Repository Code Analysis",
 
15
  layout="wide"
16
  )
17
 
18
+ # ダークテーマの設定
19
  st.markdown("""
20
  <style>
21
+ .stApp {
22
+ background-color: #0e1117;
23
+ color: #ffffff;
24
+ }
25
+ .chat-message {
26
+ padding: 1rem;
27
+ margin: 1rem 0;
28
+ border-radius: 0.5rem;
29
+ }
30
+ .assistant-message {
31
+ background-color: #1e2329;
32
+ color: #ffffff;
33
+ }
 
 
 
 
 
 
 
 
34
  </style>
35
  """, unsafe_allow_html=True)
36
 
37
  def create_download_content(files: List[FileInfo]) -> str:
38
+ content = "# スキャン結果\n\n"
39
+ for file in files:
40
+ content += f"## {file.path}\n"
41
+ content += f"サイズ: {file.formatted_size}\n"
42
+ content += f"エンコーディング: {file.encoding or '不明'}\n\n"
43
+ if file.content:
44
+ content += f"```{file.extension[1:] if file.extension else ''}\n"
45
+ content += file.content
46
+ content += "\n```\n\n"
47
+ return content
48
 
49
  def clone_repository(repo_url: str) -> Path:
50
+ temp_dir = Path(tempfile.mkdtemp())
51
+ git.Repo.clone_from(repo_url, temp_dir)
52
+ return temp_dir
53
 
54
+ # セッション状態の初期化
55
  if 'repo_content' not in st.session_state:
56
+ st.session_state.repo_content = None
57
  if 'temp_dir' not in st.session_state:
58
+ st.session_state.temp_dir = None
59
  if 'llm_service' not in st.session_state:
60
+ try:
61
+ api_key = os.getenv("ANTHROPIC_API_KEY")
62
+ if not api_key:
63
+ st.error("ANTHROPIC_API_KEY環境変数が設定されていません")
64
+ st.stop()
65
+ st.session_state.llm_service = LLMService(api_key)
66
+ except Exception as e:
67
+ st.error(str(e))
68
+ st.stop()
69
 
70
+ # メインのUIレイアウト
71
  st.title("🔍 リポジトリ解析・質問システム")
72
 
73
+ # サイドバーの設定
74
  with st.sidebar:
75
+ st.subheader("📌 使い方")
76
+ st.markdown("""
77
+ 1. スキャン対象の拡張子を選択
78
+ 2. GitHubリポジトリのURLを入力
79
+ 3. スキャンを実行
80
+ 4. コードについて質問(最大5ターンの会話が可能)
81
+ """)
82
 
83
+ # スキャン対象の拡張子選択
84
+ st.subheader("🔍 スキャン対象の選択")
85
 
86
+ # 拡張子をカテゴリごとに表示
87
+ st.write("プログラミング言語:")
88
+ prog_exts = {'.py', '.js', '.ts', '.java', '.cpp', '.hpp', '.c', '.h', '.go', '.rs'}
89
+ selected_prog = {ext: st.checkbox(ext, value=True, key=f"prog_{ext}")
90
+ for ext in prog_exts}
91
+
92
+ st.write("設定ファイル:")
93
+ config_exts = {'.json', '.yml', '.yaml', '.toml'}
94
+ selected_config = {ext: st.checkbox(ext, value=True, key=f"config_{ext}")
95
+ for ext in config_exts}
96
+
97
+ st.write("ドキュメント:")
98
+ doc_exts = {'.md', '.txt'}
99
+ selected_doc = {ext: st.checkbox(ext, value=True, key=f"doc_{ext}")
100
+ for ext in doc_exts}
101
+
102
+ # 選択された拡張子の集合を作成
103
+ selected_extensions = {ext for exts in [selected_prog, selected_config, selected_doc]
104
+ for ext, selected in exts.items() if selected}
105
 
106
+ # URLの入力
107
  repo_url = st.text_input(
108
  "GitHubリポジトリのURLを入力",
109
  placeholder="https://github.com/username/repository.git"
110
  )
111
 
112
+ # スキャン実行ボタン
113
  if st.button("スキャン開始", disabled=not repo_url):
114
+ try:
115
+ with st.spinner('リポジトリをクローン中...'):
116
+ temp_dir = clone_repository(repo_url)
117
+ st.session_state.temp_dir = temp_dir
118
+
119
+ with st.spinner('ファイルをスキャン中...'):
120
+ scanner = FileScanner(temp_dir, selected_extensions)
121
+ files = scanner.scan_files()
122
+ st.session_state.repo_content = LLMService.format_code_content(files)
123
+
124
+ st.success(f"スキャン完了: {len(files)}個のファイルを検出")
125
+
126
+ # スキャン結果のダウンロードボタン
127
+ scan_result = create_download_content(files)
128
+ st.download_button(
129
+ label="スキャン結果をダウンロード",
130
+ data=scan_result,
131
+ file_name=f"scan_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
132
+ mime="text/markdown"
133
+ )
134
+
135
+ # 新しいスキャン時に会話履歴をクリア
136
+ st.session_state.llm_service.clear_history()
137
+
138
+ except Exception as e:
139
+ st.error(f"エラーが発生しました: {str(e)}")
140
 
141
+ # スキャン完了後の質問セクション
142
+ if st.session_state.repo_content:
143
  st.divider()
144
  st.subheader("💭 コードについて質問する")
145
 
146
+ # 会話履歴の表示
147
  for message in st.session_state.llm_service.conversation_history:
148
  if message.role == "assistant":
149
  st.markdown(f'<div class="chat-message assistant-message">{message.content}</div>',
 
173
  else:
174
  st.rerun()
175
 
176
+ # セッション終了時のクリーンアップ
177
  if st.session_state.temp_dir and Path(st.session_state.temp_dir).exists():
178
+ try:
179
+ import shutil
180
+ shutil.rmtree(st.session_state.temp_dir)
181
+ except:
182
+ pass
config/settings.py CHANGED
@@ -1,10 +1,37 @@
1
  from pathlib import Path
2
  from datetime import datetime
 
3
 
4
  class Settings:
 
5
  DEFAULT_OUTPUT_DIR = Path("output")
6
  TIMESTAMP_FORMAT = "%Y%m%d_%H%M%S"
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  @classmethod
9
  def get_timestamp(cls) -> str:
10
  return datetime.now().strftime(cls.TIMESTAMP_FORMAT)
@@ -14,5 +41,5 @@ class Settings:
14
  return cls.DEFAULT_OUTPUT_DIR / f"repo_clone_{timestamp}"
15
 
16
  @classmethod
17
- def get_log_file(cls, timestamp: str) -> Path:
18
- return cls.DEFAULT_OUTPUT_DIR / f"scan_log_{timestamp}.txt"
 
1
  from pathlib import Path
2
  from datetime import datetime
3
+ from typing import Set
4
 
5
  class Settings:
6
+ # デフォルト設定
7
  DEFAULT_OUTPUT_DIR = Path("output")
8
  TIMESTAMP_FORMAT = "%Y%m%d_%H%M%S"
9
 
10
+ # デフォルトのスキャン対象拡張子
11
+ DEFAULT_EXTENSIONS = {
12
+ # プログラミング言語
13
+ '.py', # Python
14
+ '.js', # JavaScript
15
+ '.ts', # TypeScript
16
+ '.java', # Java
17
+ '.cpp', # C++
18
+ '.hpp', # C++ Header
19
+ '.c', # C
20
+ '.h', # C Header
21
+ '.go', # Go
22
+ '.rs', # Rust
23
+
24
+ # 設定ファイル
25
+ '.json', # JSON
26
+ '.yml', # YAML
27
+ '.yaml', # YAML
28
+ '.toml', # TOML
29
+
30
+ # ドキュメント
31
+ '.md', # Markdown
32
+ '.txt', # Text
33
+ }
34
+
35
  @classmethod
36
  def get_timestamp(cls) -> str:
37
  return datetime.now().strftime(cls.TIMESTAMP_FORMAT)
 
41
  return cls.DEFAULT_OUTPUT_DIR / f"repo_clone_{timestamp}"
42
 
43
  @classmethod
44
+ def get_output_file(cls, timestamp: str) -> Path:
45
+ return cls.DEFAULT_OUTPUT_DIR / f"scan_result_{timestamp}.md"
core/file_scanner.py CHANGED
@@ -1,5 +1,5 @@
1
  from pathlib import Path
2
- from typing import List, Dict, Optional
3
  from dataclasses import dataclass
4
  import chardet
5
 
@@ -21,15 +21,41 @@ class FileInfo:
21
  return f"{self.size/(1024*1024):.1f} MB"
22
 
23
  class FileScanner:
24
- TARGET_EXTENSIONS = {'.py', '.sh', '.rb', '.js', '.ts', '.java', '.cpp',
25
- '.hpp', '.c', '.h', '.go', '.rs', '.php', '.json',
26
- '.yml', '.yaml', '.toml', '.ini', '.md', '.txt'}
 
 
27
 
28
- EXCLUDED_DIRS = {'.git', '__pycache__', 'node_modules', 'venv', '.env'}
29
- MAX_FILE_SIZE = 1 * 1024 * 1024
30
-
31
- def __init__(self, base_dir: Path):
32
  self.base_dir = base_dir
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  def scan_files(self) -> List[FileInfo]:
35
  if not self.base_dir.exists():
@@ -38,19 +64,10 @@ class FileScanner:
38
  files = []
39
 
40
  for entry in self.base_dir.glob("**/*"):
41
- if (entry.is_file() and
42
- entry.suffix.lower() in self.TARGET_EXTENSIONS and
43
- not any(excluded in entry.parts for excluded in self.EXCLUDED_DIRS) and
44
- entry.stat().st_size <= self.MAX_FILE_SIZE):
45
 
46
- try:
47
- with entry.open('rb') as f:
48
- raw_data = f.read(4096)
49
- encoding = chardet.detect(raw_data)['encoding'] or 'utf-8'
50
-
51
- with entry.open('r', encoding=encoding) as f:
52
- content = f.read()
53
-
54
  files.append(FileInfo(
55
  path=entry.absolute(),
56
  size=entry.stat().st_size,
@@ -58,7 +75,5 @@ class FileScanner:
58
  content=content,
59
  encoding=encoding
60
  ))
61
- except:
62
- continue
63
 
64
  return sorted(files, key=lambda x: str(x.path))
 
1
  from pathlib import Path
2
+ from typing import List, Dict, Optional, Set
3
  from dataclasses import dataclass
4
  import chardet
5
 
 
21
  return f"{self.size/(1024*1024):.1f} MB"
22
 
23
  class FileScanner:
24
+ # スキャン対象から除外するディレクトリ
25
+ EXCLUDED_DIRS = {
26
+ '.git', '__pycache__', 'node_modules', 'venv', '.env',
27
+ 'build', 'dist', 'target', 'bin', 'obj'
28
+ }
29
 
30
+ def __init__(self, base_dir: Path, target_extensions: Set[str]):
 
 
 
31
  self.base_dir = base_dir
32
+ self.target_extensions = target_extensions
33
+
34
+ def _should_scan_file(self, path: Path) -> bool:
35
+ if any(excluded in path.parts for excluded in self.EXCLUDED_DIRS):
36
+ return False
37
+ return path.suffix.lower() in self.target_extensions
38
+
39
+ def _read_file_content(self, file_path: Path) -> tuple[Optional[str], Optional[str]]:
40
+ try:
41
+ with file_path.open('rb') as f:
42
+ raw_data = f.read(4096)
43
+ result = chardet.detect(raw_data)
44
+
45
+ encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8'
46
+
47
+ try:
48
+ with file_path.open('r', encoding=encoding) as f:
49
+ return f.read(), encoding
50
+ except UnicodeDecodeError:
51
+ try:
52
+ with file_path.open('r', encoding='cp932') as f:
53
+ return f.read(), 'cp932'
54
+ except UnicodeDecodeError:
55
+ return None, None
56
+
57
+ except (OSError, ValueError):
58
+ return None, None
59
 
60
  def scan_files(self) -> List[FileInfo]:
61
  if not self.base_dir.exists():
 
64
  files = []
65
 
66
  for entry in self.base_dir.glob("**/*"):
67
+ if entry.is_file() and self._should_scan_file(entry):
68
+ content, encoding = self._read_file_content(entry)
 
 
69
 
70
+ if content is not None:
 
 
 
 
 
 
 
71
  files.append(FileInfo(
72
  path=entry.absolute(),
73
  size=entry.stat().st_size,
 
75
  content=content,
76
  encoding=encoding
77
  ))
 
 
78
 
79
  return sorted(files, key=lambda x: str(x.path))
scan.sh CHANGED
@@ -1,49 +1,8 @@
1
  #!/bin/bash
2
-
3
- # エラーが発生した場合に停止
4
- set -e
5
-
6
- # デフォルトのターゲットパスを設定
7
- # ここを変更することで対象を変更できます
8
- TARGET_PATH="https://github.com/DeL-TaiseiOzaki/idebate_scraping.git" # 例: Linuxカーネル
9
- # TARGET_PATH="/path/to/your/directory" # ローカルディレクトリの例
10
-
11
- # 必要なディレクトリの存在確認
12
- if [ ! -d "output" ]; then
13
- mkdir output
14
- fi
15
-
16
- # Pythonの存在確認
17
- if ! command -v python3 &> /dev/null; then
18
- echo "Error: Python3 is not installed"
19
  exit 1
20
  fi
21
 
22
- # GitHubリポジトリの場合、Gitの存在確認
23
- if [[ $TARGET_PATH == http* ]] && [[ $TARGET_PATH == *github.com* ]]; then
24
- if ! command -v git &> /dev/null; then
25
- echo "Error: Git is not installed"
26
- exit 1
27
- fi
28
- echo "Scanning GitHub repository: $TARGET_PATH"
29
- else
30
- if [ ! -d "$TARGET_PATH" ]; then
31
- echo "Error: Directory not found: $TARGET_PATH"
32
- exit 1
33
- fi
34
- echo "Scanning local directory: $TARGET_PATH"
35
- fi
36
-
37
- # スキャンの実行
38
- echo "Starting directory scan..."
39
- python3 main.py "$TARGET_PATH"
40
-
41
- exit_code=$?
42
-
43
- if [ $exit_code -eq 0 ]; then
44
- echo "Scan completed successfully!"
45
- echo "Results are saved in the 'output' directory"
46
- else
47
- echo "Scan failed with exit code: $exit_code"
48
- exit $exit_code
49
- fi
 
1
  #!/bin/bash
2
+ if [ $# -ne 1 ]; then
3
+ echo "Usage: ./scan.sh <github_url or directory_path>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  exit 1
5
  fi
6
 
7
+ target_path="$1"
8
+ python main.py "$target_path"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/llm_service.py CHANGED
@@ -1,7 +1,6 @@
1
- from typing import Optional, List, Dict
2
  import anthropic
3
  from dataclasses import dataclass
4
- from config.llm_settings import LLMSettings
5
  from core.file_scanner import FileInfo
6
 
7
  @dataclass
@@ -12,15 +11,10 @@ class Message:
12
  class LLMService:
13
  MAX_TURNS = 5
14
 
15
- def __init__(self):
16
- self.settings = LLMSettings()
17
- self.claude_client = anthropic.Anthropic(api_key=self.settings.anthropic_api_key)
18
  self.conversation_history: List[Message] = []
19
 
20
- def switch_model(self, model: str):
21
- if model.lower() != "claude":
22
- raise ValueError("Only Claude model is available")
23
-
24
  def create_prompt(self, content: str, query: str) -> str:
25
  return f"""以下はGitHubリポジトリのコード解析結果です。このコードについて質問に答えてください。
26
 
@@ -30,36 +24,22 @@ class LLMService:
30
  質問: {query}
31
 
32
  できるだけ具体的に、コードの内容を参照しながら回答してください。"""
33
-
34
  def _add_to_history(self, role: str, content: str):
35
  self.conversation_history.append(Message(role=role, content=content))
36
  if len(self.conversation_history) > self.MAX_TURNS * 2:
37
  self.conversation_history = self.conversation_history[-self.MAX_TURNS * 2:]
38
 
39
- def _format_messages_for_claude(self) -> List[Dict[str, str]]:
40
- return [{"role": msg.role, "content": msg.content}
41
- for msg in self.conversation_history]
42
-
43
- def get_conversation_history(self) -> List[Dict[str, str]]:
44
- return [{"role": msg.role, "content": msg.content}
45
- for msg in self.conversation_history]
46
-
47
- def clear_history(self):
48
- self.conversation_history = []
49
-
50
  def get_response(self, content: str, query: str) -> tuple[Optional[str], Optional[str]]:
51
- """LLMを使用して回答を生成"""
52
  try:
53
  prompt = self.create_prompt(content, query)
54
  self._add_to_history("user", prompt)
55
 
56
- # Claudeへのリクエストを修正
57
- response = self.claude_client.messages.create(
58
- model="claude-3-sonnet-20240229",
59
- max_tokens=1024,
60
- messages=[
61
- {"role": "user", "content": prompt}
62
- ]
63
  )
64
 
65
  answer = response.content[0].text
@@ -68,7 +48,10 @@ class LLMService:
68
 
69
  except Exception as e:
70
  return None, f"エラーが発生しました: {str(e)}"
71
-
 
 
 
72
  @staticmethod
73
  def format_code_content(files: List[FileInfo]) -> str:
74
  formatted_content = []
@@ -76,4 +59,4 @@ class LLMService:
76
  formatted_content.append(
77
  f"#ファイルパス\n{file_info.path}\n------------\n{file_info.content}\n"
78
  )
79
- return "\n".join(formatted_content)
 
 
1
  import anthropic
2
  from dataclasses import dataclass
3
+ from typing import List, Optional, Dict
4
  from core.file_scanner import FileInfo
5
 
6
  @dataclass
 
11
  class LLMService:
12
  MAX_TURNS = 5
13
 
14
+ def __init__(self, api_key: str):
15
+ self.client = anthropic.Anthropic(api_key=api_key)
 
16
  self.conversation_history: List[Message] = []
17
 
 
 
 
 
18
  def create_prompt(self, content: str, query: str) -> str:
19
  return f"""以下はGitHubリポジトリのコード解析結果です。このコードについて質問に答えてください。
20
 
 
24
  質問: {query}
25
 
26
  できるだけ具体的に、コードの内容を参照しながら回答してください。"""
27
+
28
  def _add_to_history(self, role: str, content: str):
29
  self.conversation_history.append(Message(role=role, content=content))
30
  if len(self.conversation_history) > self.MAX_TURNS * 2:
31
  self.conversation_history = self.conversation_history[-self.MAX_TURNS * 2:]
32
 
 
 
 
 
 
 
 
 
 
 
 
33
  def get_response(self, content: str, query: str) -> tuple[Optional[str], Optional[str]]:
 
34
  try:
35
  prompt = self.create_prompt(content, query)
36
  self._add_to_history("user", prompt)
37
 
38
+ response = self.client.messages.create(
39
+ model="claude-3-5-sonnet-latest",
40
+ messages=[{"role": msg.role, "content": msg.content}
41
+ for msg in self.conversation_history],
42
+ max_tokens=1024
 
 
43
  )
44
 
45
  answer = response.content[0].text
 
48
 
49
  except Exception as e:
50
  return None, f"エラーが発生しました: {str(e)}"
51
+
52
+ def clear_history(self):
53
+ self.conversation_history = []
54
+
55
  @staticmethod
56
  def format_code_content(files: List[FileInfo]) -> str:
57
  formatted_content = []
 
59
  formatted_content.append(
60
  f"#ファイルパス\n{file_info.path}\n------------\n{file_info.content}\n"
61
  )
62
+ return "\n".join(formatted_content)