DeL-TaiseiOzaki commited on
Commit
560aacd
·
1 Parent(s): fcac1a1
app.py CHANGED
@@ -1,177 +1,89 @@
1
  import streamlit as st
2
  import tempfile
3
  import git
4
- from core.file_scanner import FileScanner
5
  from pathlib import Path
6
- from datetime import datetime
7
  from services.llm_service import LLMService
8
- from core.file_scanner import FileInfo
9
  from typing import List
10
 
11
- # ページ設定
12
  st.set_page_config(
13
- page_title="Repository Code Analysis",
14
- page_icon="🔍",
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
- .stButton button {
35
- background-color: #2ea44f;
36
- color: #ffffff;
37
- }
38
- .stTextArea textarea {
39
- background-color: #1e2329;
40
- color: #ffffff;
41
- }
42
  </style>
43
  """, unsafe_allow_html=True)
44
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  def clone_repository(repo_url: str) -> Path:
47
- """リポジトリをクローンして一時ディレクトリに保存"""
48
- temp_dir = Path(tempfile.mkdtemp())
49
- git.Repo.clone_from(repo_url, temp_dir)
50
- return temp_dir
51
-
52
- def create_download_content(files: List[FileInfo]) -> str:
53
- content = "# スキャン結果\n\n"
54
- for file in files:
55
- content += f"## {file.path}\n"
56
- content += f"サイズ: {file.formatted_size}\n"
57
- content += f"エンコーディング: {file.encoding or '不明'}\n\n"
58
- if file.content:
59
- content += f"```{file.extension[1:] if file.extension else ''}\n"
60
- content += file.content
61
- content += "\n```\n\n"
62
- return content
63
 
64
- # セッション状態の初期化
65
  if 'repo_content' not in st.session_state:
66
- st.session_state.repo_content = 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
- st.session_state.llm_service = LLMService()
72
- except ValueError as e:
73
- st.error(str(e))
74
- st.stop()
75
 
76
- # メインのUIレイアウト
77
  st.title("🔍 リポジトリ解析・質問システム")
78
 
79
- # サイドバーでモデル選択
80
- available_models = st.session_state.llm_service.settings.get_available_models()
81
- if len(available_models) > 1:
82
- selected_model = st.sidebar.selectbox(
83
- "使用するモデル",
84
- available_models,
85
- index=available_models.index(st.session_state.llm_service.current_model)
86
- )
87
- st.session_state.llm_service.switch_model(selected_model)
88
-
89
- # URLの入力
90
- repo_url = st.text_input(
91
- "GitHubリポジトリのURLを入力",
92
- placeholder="https://github.com/username/repository.git"
93
- )
94
-
95
- # スキャン実行ボタン
96
- if st.button("スキャン開始", disabled=not repo_url):
97
- try:
98
- with st.spinner('リポジトリをクローン中...'):
99
- temp_dir = clone_repository(repo_url)
100
- st.session_state.temp_dir = temp_dir
101
-
102
- with st.spinner('ファイルをスキャン中...'):
103
- scanner = FileScanner(temp_dir)
104
- files = scanner.scan_files() # List[FileInfo] を取得
105
- st.session_state.repo_content = LLMService.format_code_content(files)
106
-
107
- st.success(f"スキャン完了: {len(files)}個のファイルを検出")
108
- # 新しいスキャン時に会話履歴をクリア
109
- st.session_state.llm_service.clear_history()
110
-
111
- except Exception as e:
112
- st.error(f"エラーが発生しました: {str(e)}")
113
-
114
- # スキャン完了後の質問セクション
115
- if st.session_state.repo_content:
116
- st.divider()
117
- st.subheader("💭 コードについて質問する")
118
-
119
- # スキャン結果のダウンロードボタン
120
- scan_result = create_download_content(files) # filesはスキャン結果
121
- st.download_button(
122
- label="スキャン結果をダウンロード",
123
- data=scan_result,
124
- file_name=f"scan_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
125
- mime="text/markdown"
126
- )
127
-
128
- # 会話履歴の表示(アシスタントの回答のみ)
129
- for message in st.session_state.llm_service.conversation_history:
130
- if message.role == "assistant": # アシスタントの回答のみを表示
131
- st.markdown(f'<div class="chat-message assistant-message">{message.content}</div>',
132
- unsafe_allow_html=True)
133
-
134
- query = st.text_area(
135
- "質問を入力してください",
136
- placeholder="例: このコードの主な機能は何ですか?"
137
- )
138
-
139
- col1, col2 = st.columns([1, 5])
140
- with col1:
141
- if st.button("履歴クリア"):
142
- st.session_state.llm_service.clear_history()
143
- st.rerun()
144
 
145
- with col2:
146
- if st.button("質問する", disabled=not query):
147
- with st.spinner('回答を生成中...'):
148
- response, error = st.session_state.llm_service.get_response(
149
- st.session_state.repo_content,
150
- query
151
- )
152
-
153
- if error:
154
- st.error(error)
155
- else:
156
- st.rerun() # 会話履歴を更新するために再表示
157
-
158
- # セッション終了時のクリーンアップ
159
- if st.session_state.temp_dir and Path(st.session_state.temp_dir).exists():
160
- try:
161
- import shutil
162
- shutil.rmtree(st.session_state.temp_dir)
163
- except:
164
- pass
165
 
166
- # サイドバー情報
167
- with st.sidebar:
168
  st.subheader("📌 使い方")
169
  st.markdown("""
170
  1. GitHubリポジトリのURLを入力
171
  2. スキャンを実行
172
  3. コードについて質問(最大5ターンの会話が可能)
173
  """)
174
-
175
  st.subheader("🔍 スキャン対象")
176
  st.markdown("""
177
  - Python (.py)
@@ -179,4 +91,75 @@ with st.sidebar:
179
  - Java (.java)
180
  - C/C++ (.c, .h, .cpp, .hpp)
181
  - その他の主要なプログラミング言語
182
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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",
12
+ page_icon="🔍",
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
+ st.divider()
 
80
  st.subheader("📌 使い方")
81
  st.markdown("""
82
  1. GitHubリポジトリのURLを入力
83
  2. スキャンを実行
84
  3. コードについて質問(最大5ターンの会話が可能)
85
  """)
86
+
87
  st.subheader("🔍 スキャン対象")
88
  st.markdown("""
89
  - Python (.py)
 
91
  - Java (.java)
92
  - C/C++ (.c, .h, .cpp, .hpp)
93
  - その他の主要なプログラミング言語
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:
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>',
134
+ unsafe_allow_html=True)
135
+
136
+ query = st.text_area(
137
+ "質問を入力してください",
138
+ placeholder="例: このコードの主な機能は何ですか?"
139
+ )
140
+
141
+ col1, col2 = st.columns([1, 5])
142
+ with col1:
143
+ if st.button("履歴クリア"):
144
+ st.session_state.llm_service.clear_history()
145
+ st.rerun()
146
+
147
+ with col2:
148
+ if st.button("質問する", disabled=not query):
149
+ with st.spinner('回答を生成中...'):
150
+ response, error = st.session_state.llm_service.get_response(
151
+ st.session_state.repo_content,
152
+ query
153
+ )
154
+
155
+ if error:
156
+ st.error(error)
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
config/llm_settings.py CHANGED
@@ -5,15 +5,10 @@ class LLMSettings:
5
  def __init__(self):
6
  load_dotenv()
7
  self.anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
8
- self.openai_api_key = os.getenv("OPENAI_API_KEY")
9
  self.default_llm = "claude"
 
 
 
10
 
11
  def get_available_models(self):
12
- available_models = []
13
- if self.anthropic_api_key:
14
- available_models.append("claude")
15
- if self.openai_api_key:
16
- available_models.append("openai")
17
- if not available_models:
18
- raise ValueError("APIキーが設定されていません")
19
- return available_models
 
5
  def __init__(self):
6
  load_dotenv()
7
  self.anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
 
8
  self.default_llm = "claude"
9
+
10
+ if not self.anthropic_api_key:
11
+ raise ValueError("ANTHROPIC_API_KEY is required")
12
 
13
  def get_available_models(self):
14
+ return ["claude"]
 
 
 
 
 
 
 
config/settings.py CHANGED
@@ -14,5 +14,5 @@ class Settings:
14
  return cls.DEFAULT_OUTPUT_DIR / f"repo_clone_{timestamp}"
15
 
16
  @classmethod
17
- def get_output_file(cls, timestamp: str) -> Path:
18
- return cls.DEFAULT_OUTPUT_DIR / f"scan_result_{timestamp}.txt"
 
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"
core/file_scanner.py CHANGED
@@ -1,60 +1,64 @@
1
  from pathlib import Path
2
  from typing import List, Dict, Optional
3
  from dataclasses import dataclass
 
4
 
5
  @dataclass
6
  class FileInfo:
7
  path: Path
 
 
8
  content: Optional[str] = None
 
 
 
 
 
 
 
 
 
 
9
 
10
  class FileScanner:
11
- # スキャン対象の拡張子
12
- TARGET_EXTENSIONS = {
13
- '.py', '.js', '.java', '.cpp', '.hpp', '.c', '.h',
14
- '.go', '.rs', '.php', '.rb', '.ts', '.scala', '.kt',
15
- '.cs', '.swift', '.m', '.sh', '.pl', '.r'
16
- }
17
 
18
- # スキャン対象から除外するディレクトリ
19
- EXCLUDED_DIRS = {
20
- '.git', '__pycache__', 'node_modules', 'venv', '.env',
21
- 'build', 'dist', 'target', 'bin', 'obj'
22
- }
23
 
24
  def __init__(self, base_dir: Path):
25
  self.base_dir = base_dir
26
 
27
- def _should_scan_file(self, path: Path) -> bool:
28
- if any(excluded in path.parts for excluded in self.EXCLUDED_DIRS):
29
- return False
30
- return path.suffix.lower() in self.TARGET_EXTENSIONS
31
-
32
- def _read_file_content(self, file_path: Path) -> Optional[str]:
33
- try:
34
- # まずUTF-8で試す
35
- try:
36
- with file_path.open('r', encoding='utf-8') as f:
37
- return f.read()
38
- except UnicodeDecodeError:
39
- # UTF-8で失敗したらcp932を試す
40
- with file_path.open('r', encoding='cp932') as f:
41
- return f.read()
42
- except (OSError, UnicodeDecodeError):
43
- return None
44
-
45
  def scan_files(self) -> List[FileInfo]:
46
  if not self.base_dir.exists():
47
- raise FileNotFoundError(f"Directory not found: {self.base_dir}")
48
 
49
  files = []
50
 
51
- for entry in self.base_dir.rglob('*'):
52
- if entry.is_file() and self._should_scan_file(entry):
53
- content = self._read_file_content(entry)
54
- if content is not None:
 
 
 
 
 
 
 
 
 
 
55
  files.append(FileInfo(
56
- path=entry.relative_to(self.base_dir),
57
- content=content
 
 
 
58
  ))
 
 
59
 
60
  return sorted(files, key=lambda x: str(x.path))
 
1
  from pathlib import Path
2
  from typing import List, Dict, Optional
3
  from dataclasses import dataclass
4
+ import chardet
5
 
6
  @dataclass
7
  class FileInfo:
8
  path: Path
9
+ size: int
10
+ extension: str
11
  content: Optional[str] = None
12
+ encoding: Optional[str] = None
13
+
14
+ @property
15
+ def formatted_size(self) -> str:
16
+ if self.size < 1024:
17
+ return f"{self.size} B"
18
+ elif self.size < 1024 * 1024:
19
+ return f"{self.size/1024:.1f} KB"
20
+ else:
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():
36
+ raise FileNotFoundError(f"ディレクトリが見つかりません: {self.base_dir}")
37
 
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,
57
+ extension=entry.suffix.lower(),
58
+ content=content,
59
+ encoding=encoding
60
  ))
61
+ except:
62
+ continue
63
 
64
  return sorted(files, key=lambda x: str(x.path))
services/llm_service.py CHANGED
@@ -1,5 +1,4 @@
1
- from typing import Optional, List, Dict, Any
2
- import openai
3
  import anthropic
4
  from dataclasses import dataclass
5
  from config.llm_settings import LLMSettings
@@ -14,26 +13,15 @@ class LLMService:
14
  MAX_TURNS = 5
15
 
16
  def __init__(self):
17
- """LLMサービスの初期化"""
18
  self.settings = LLMSettings()
19
- self.current_model = self.settings.default_llm
20
-
21
- # API クライアントの初期化
22
- if self.settings.anthropic_api_key:
23
- self.claude_client = anthropic.Anthropic(api_key=self.settings.anthropic_api_key)
24
- if self.settings.openai_api_key:
25
- openai.api_key = self.settings.openai_api_key
26
-
27
  self.conversation_history: List[Message] = []
28
 
29
  def switch_model(self, model: str):
30
- """使用するモデルを切り替え"""
31
- if model not in self.settings.get_available_models():
32
- raise ValueError(f"モデル {model} は利用できません")
33
- self.current_model = model
34
 
35
  def create_prompt(self, content: str, query: str) -> str:
36
- """プロンプトを生成"""
37
  return f"""以下はGitHubリポジトリのコード解析結果です。このコードについて質問に答えてください。
38
 
39
  コード解析結果:
@@ -44,54 +32,31 @@ class LLMService:
44
  できるだけ具体的に、コードの内容を参照しながら回答してください。"""
45
 
46
  def _add_to_history(self, role: str, content: str):
47
- """会話履歴に追加(最大5ターン)"""
48
  self.conversation_history.append(Message(role=role, content=content))
49
- # 最大ターン数を超えた場合、古い会話を削除
50
- if len(self.conversation_history) > self.MAX_TURNS * 2: # 各ターンは質問と回答で2メッセージ
51
  self.conversation_history = self.conversation_history[-self.MAX_TURNS * 2:]
52
 
53
  def _format_messages_for_claude(self) -> List[Dict[str, str]]:
54
- """Claude用にメッセージをフォーマット"""
55
  return [{"role": msg.role, "content": msg.content}
56
  for msg in self.conversation_history]
57
 
58
- def _format_messages_for_gpt(self) -> List[Dict[str, str]]:
59
- """GPT用にメッセージをフォーマット"""
60
- return [
61
- {"role": "system", "content": "あなたはコードアナリストとして、リポジトリの解析と質問への回答を行います。"},
62
- *[{"role": msg.role, "content": msg.content}
63
- for msg in self.conversation_history]
64
- ]
65
-
66
  def get_conversation_history(self) -> List[Dict[str, str]]:
67
- """会話履歴を取得"""
68
  return [{"role": msg.role, "content": msg.content}
69
  for msg in self.conversation_history]
70
 
71
  def clear_history(self):
72
- """会話履歴をクリア"""
73
  self.conversation_history = []
74
 
75
  def get_response(self, content: str, query: str) -> tuple[Optional[str], Optional[str]]:
76
- """LLMを使用して回答を生成"""
77
  try:
78
  prompt = self.create_prompt(content, query)
79
  self._add_to_history("user", prompt)
80
 
81
- if self.current_model == 'claude':
82
- response = self.claude_client.messages.create(
83
- model="claude-3-sonnet-20240229",
84
- max_tokens=4000,
85
- messages=self._format_messages_for_claude()
86
- )
87
- answer = response.content[0].text
88
-
89
- else: # gpt
90
- response = openai.ChatCompletion.create(
91
- model="gpt-4o",
92
- messages=self._format_messages_for_gpt()
93
- )
94
- answer = response.choices[0].message.content
95
 
96
  self._add_to_history("assistant", answer)
97
  return answer, None
@@ -101,7 +66,6 @@ class LLMService:
101
 
102
  @staticmethod
103
  def format_code_content(files: List[FileInfo]) -> str:
104
- """ファイル内容をプロンプト用にフォーマット"""
105
  formatted_content = []
106
  for file_info in files:
107
  formatted_content.append(
 
1
+ from typing import Optional, List, Dict
 
2
  import anthropic
3
  from dataclasses import dataclass
4
  from config.llm_settings import LLMSettings
 
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
 
27
  コード解析結果:
 
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
  try:
52
  prompt = self.create_prompt(content, query)
53
  self._add_to_history("user", prompt)
54
 
55
+ response = self.claude_client.messages.create(
56
+ model="claude-3-5-sonnet-latest",
57
+ messages=self._format_messages_for_claude()
58
+ )
59
+ answer = response.content[0].text
 
 
 
 
 
 
 
 
 
60
 
61
  self._add_to_history("assistant", answer)
62
  return answer, None
 
66
 
67
  @staticmethod
68
  def format_code_content(files: List[FileInfo]) -> str:
 
69
  formatted_content = []
70
  for file_info in files:
71
  formatted_content.append(