thonny-codemate 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {thonny_codemate-0.1.4.dist-info → thonny_codemate-0.1.5.dist-info}/METADATA +10 -3
- {thonny_codemate-0.1.4.dist-info → thonny_codemate-0.1.5.dist-info}/RECORD +8 -8
- {thonny_codemate-0.1.4.dist-info → thonny_codemate-0.1.5.dist-info}/licenses/LICENSE +1 -1
- thonnycontrib/thonny_codemate/__init__.py +1 -1
- thonnycontrib/thonny_codemate/edit_mode_handler.py +218 -0
- thonnycontrib/thonny_codemate/ui/chat_view_html.py +174 -0
- thonnycontrib/__init__.py +0 -1
- {thonny_codemate-0.1.4.dist-info → thonny_codemate-0.1.5.dist-info}/WHEEL +0 -0
- {thonny_codemate-0.1.4.dist-info → thonny_codemate-0.1.5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: thonny-codemate
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.5
|
4
4
|
Summary: A Thonny IDE plugin that provides AI-powered coding assistance using local and cloud LLMs
|
5
5
|
Author-email: tokoroten <shinta.nakayama@gmail.com>
|
6
6
|
License: MIT
|
@@ -174,8 +174,15 @@ huggingface-cli download bartowski/Llama-3.2-1B-Instruct-GGUF Llama-3.2-1B-Instr
|
|
174
174
|
4. **Code Generation**:
|
175
175
|
- Write a comment describing what you want
|
176
176
|
- Right-click and choose "Generate from Comment"
|
177
|
-
|
178
|
-
|
177
|
+
5. **Edit Mode (New in v0.1.5)**:
|
178
|
+
- Switch to "Edit" mode in the LLM Assistant panel
|
179
|
+
- Type your modification request (e.g., "Add error handling to this function")
|
180
|
+
- The AI will modify your code directly in the editor
|
181
|
+
- Works with selected text or entire file
|
182
|
+
6. **Interactive Chat**:
|
183
|
+
- Use the AI Assistant panel for general questions
|
184
|
+
- Include context from your current file with the checkbox
|
185
|
+
7. **Error Fixing**:
|
179
186
|
- When you encounter an error, click "Explain Error" in the assistant panel
|
180
187
|
- The AI will analyze the error and suggest fixes
|
181
188
|
|
@@ -1,8 +1,8 @@
|
|
1
|
-
thonny_codemate-0.1.
|
2
|
-
thonnycontrib/__init__.py,sha256=
|
3
|
-
thonnycontrib/thonny_codemate/__init__.py,sha256=vslo9LDAl7QUJD5DAM6RjelXiu1_nIvLtS2FTzhskgU,13487
|
1
|
+
thonny_codemate-0.1.5.dist-info/licenses/LICENSE,sha256=6Mww21sIIHwlMy-By5wl0oPcegzkWF2wE0vi_ZSzz3g,1065
|
2
|
+
thonnycontrib/thonny_codemate/__init__.py,sha256=5uJvG0Oah15EVvARAZxN4neMm2xJcsOBRw_xRcaEG4g,13487
|
4
3
|
thonnycontrib/thonny_codemate/api.py,sha256=YLXjPsB6cykgRwyjzrUBOGFtw0eu4BQgr1AiMbj8e7c,4259
|
5
4
|
thonnycontrib/thonny_codemate/context_manager.py,sha256=K83M-VlBy7DR1xzedyPT8NViqnR9CmX95YmXnDBzRFM,11274
|
5
|
+
thonnycontrib/thonny_codemate/edit_mode_handler.py,sha256=7i-FTETFcIOu7lWjPa-zXH27gWY4w9VRg5YcSQ_wzDQ,7612
|
6
6
|
thonnycontrib/thonny_codemate/external_providers.py,sha256=PmIRY_lLOILCAZa4mpP3yB9PU7ZWSddWjYPf2S7vkyY,29016
|
7
7
|
thonnycontrib/thonny_codemate/i18n.py,sha256=tq1pBwNLYgQtJBXH36MmTOl1sRuaPaXgSyMaAXVIIPU,28846
|
8
8
|
thonnycontrib/thonny_codemate/llm_client.py,sha256=Ojk8oxNI7tKAzVEtaoSwOLVisT1cOYdXf9Qw56eC2L4,33767
|
@@ -12,7 +12,7 @@ thonnycontrib/thonny_codemate/performance_monitor.py,sha256=L6gIin6oBzplRgZB676A
|
|
12
12
|
thonnycontrib/thonny_codemate/prompts.py,sha256=bV4Qjr8SjqWBnnUIk-g-R58sRGbyZOauvlWvNO9GkGg,3885
|
13
13
|
thonnycontrib/thonny_codemate/ui/__init__.py,sha256=SDKZ1vYdsb14_jHIP4wTeM8Vqkog8z0PkWquS3-mQIY,43
|
14
14
|
thonnycontrib/thonny_codemate/ui/chat_view.py,sha256=veyFSCJUOPVFUt1qJochKTvHQi49u3fzxKQ0Nti0NNc,29203
|
15
|
-
thonnycontrib/thonny_codemate/ui/chat_view_html.py,sha256=
|
15
|
+
thonnycontrib/thonny_codemate/ui/chat_view_html.py,sha256=yul_leokkFdf_LGEME8FYVfPSA_JNP_f3Hv4QyFvDew,60866
|
16
16
|
thonnycontrib/thonny_codemate/ui/custom_prompt_dialog.py,sha256=LEemjMmHM_KcidEsUhOOffa1DzCauOXNiUqlH2wGjts,5973
|
17
17
|
thonnycontrib/thonny_codemate/ui/markdown_renderer.py,sha256=K1vHLTn6EocPRxb7fXMKGsSFD6iY-OhFTqMY_dBkiRw,15649
|
18
18
|
thonnycontrib/thonny_codemate/ui/model_download_dialog.py,sha256=1qzqCL_2J_cXCh9OvUdaPAyouBm76BgfyUlPRkNCDGw,13701
|
@@ -21,7 +21,7 @@ thonnycontrib/thonny_codemate/utils/__init__.py,sha256=xjA_6r7WFCf3RbCNnZhwFOSap
|
|
21
21
|
thonnycontrib/thonny_codemate/utils/constants.py,sha256=l6KKItPClPy7IWZKH53Kpq-PNMfJogTYIZ2OGKTZO8w,3159
|
22
22
|
thonnycontrib/thonny_codemate/utils/error_messages.py,sha256=kOucGfSwBIOE23GsVAIcQYY7YbKjezIuOxiIM1wTKY4,3601
|
23
23
|
thonnycontrib/thonny_codemate/utils/unified_error_handler.py,sha256=yvrSiXC3Kzro4KLL6lDLMBRfdff79PLvBF1yjHFhv9E,10769
|
24
|
-
thonny_codemate-0.1.
|
25
|
-
thonny_codemate-0.1.
|
26
|
-
thonny_codemate-0.1.
|
27
|
-
thonny_codemate-0.1.
|
24
|
+
thonny_codemate-0.1.5.dist-info/METADATA,sha256=g1mOYKzg0jD93DgLMlOMsY35otT9PCIEewvjf8NraDg,11227
|
25
|
+
thonny_codemate-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
26
|
+
thonny_codemate-0.1.5.dist-info/top_level.txt,sha256=thF9WQtY_lcNKOOR4SQeeWLAVWb4i8XhdGC7pEMAOHY,14
|
27
|
+
thonny_codemate-0.1.5.dist-info/RECORD,,
|
@@ -0,0 +1,218 @@
|
|
1
|
+
"""
|
2
|
+
Edit mode handler for single file editing
|
3
|
+
Inspired by VSCode Copilot Chat's edit functionality
|
4
|
+
"""
|
5
|
+
import re
|
6
|
+
import tkinter as tk
|
7
|
+
from typing import Optional, Tuple, List
|
8
|
+
from pathlib import Path
|
9
|
+
import difflib
|
10
|
+
import logging
|
11
|
+
|
12
|
+
from thonny import get_workbench
|
13
|
+
from .i18n import tr
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
class EditModeHandler:
|
19
|
+
"""Handles edit mode functionality for modifying code in the current file"""
|
20
|
+
|
21
|
+
# Prompt template for edit mode
|
22
|
+
EDIT_PROMPT_TEMPLATE = """You are an AI programming assistant specialized in modifying code.
|
23
|
+
|
24
|
+
Instructions:
|
25
|
+
1. Provide the complete modified code
|
26
|
+
2. Use '# ...existing code...' to represent unchanged regions (this saves tokens)
|
27
|
+
3. Preserve the original indentation and coding style
|
28
|
+
4. Focus only on the requested changes
|
29
|
+
5. Include helpful comments for significant changes
|
30
|
+
|
31
|
+
Current file: {filename}
|
32
|
+
Language: {language}
|
33
|
+
|
34
|
+
Current code:
|
35
|
+
```{language}
|
36
|
+
{content}
|
37
|
+
```
|
38
|
+
|
39
|
+
{selection_info}
|
40
|
+
|
41
|
+
User request: {user_prompt}
|
42
|
+
|
43
|
+
Please provide the modified code. Start your response with a code block containing the updated code:
|
44
|
+
"""
|
45
|
+
|
46
|
+
SELECTION_TEMPLATE = """Selected region (lines {start_line}-{end_line}):
|
47
|
+
```{language}
|
48
|
+
{selected_text}
|
49
|
+
```
|
50
|
+
|
51
|
+
Focus your changes primarily on the selected region, but you may modify other parts if necessary.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(self, llm_client):
|
55
|
+
self.llm_client = llm_client
|
56
|
+
self.workbench = get_workbench()
|
57
|
+
|
58
|
+
def build_edit_prompt(self, user_prompt: str, filename: str, content: str,
|
59
|
+
selection: Optional[Tuple[str, int, int]] = None) -> str:
|
60
|
+
"""Build the prompt for edit mode"""
|
61
|
+
# Detect language from filename
|
62
|
+
language = self._detect_language(filename)
|
63
|
+
|
64
|
+
# Handle selection if provided
|
65
|
+
selection_info = ""
|
66
|
+
if selection:
|
67
|
+
selected_text, start_line, end_line = selection
|
68
|
+
selection_info = self.SELECTION_TEMPLATE.format(
|
69
|
+
language=language,
|
70
|
+
selected_text=selected_text,
|
71
|
+
start_line=start_line,
|
72
|
+
end_line=end_line
|
73
|
+
)
|
74
|
+
|
75
|
+
return self.EDIT_PROMPT_TEMPLATE.format(
|
76
|
+
filename=filename or "Untitled",
|
77
|
+
language=language,
|
78
|
+
content=content,
|
79
|
+
selection_info=selection_info,
|
80
|
+
user_prompt=user_prompt
|
81
|
+
)
|
82
|
+
|
83
|
+
def _detect_language(self, filename: str) -> str:
|
84
|
+
"""Detect programming language from filename"""
|
85
|
+
if not filename:
|
86
|
+
return "python"
|
87
|
+
|
88
|
+
ext = Path(filename).suffix.lower()
|
89
|
+
language_map = {
|
90
|
+
'.py': 'python',
|
91
|
+
'.js': 'javascript',
|
92
|
+
'.java': 'java',
|
93
|
+
'.cpp': 'cpp',
|
94
|
+
'.c': 'c',
|
95
|
+
'.cs': 'csharp',
|
96
|
+
'.rb': 'ruby',
|
97
|
+
'.go': 'go',
|
98
|
+
'.rs': 'rust',
|
99
|
+
'.php': 'php',
|
100
|
+
'.html': 'html',
|
101
|
+
'.css': 'css',
|
102
|
+
'.sql': 'sql',
|
103
|
+
'.sh': 'bash',
|
104
|
+
'.yml': 'yaml',
|
105
|
+
'.yaml': 'yaml',
|
106
|
+
'.json': 'json',
|
107
|
+
'.xml': 'xml'
|
108
|
+
}
|
109
|
+
return language_map.get(ext, 'text')
|
110
|
+
|
111
|
+
def extract_code_block(self, response: str) -> Optional[str]:
|
112
|
+
"""Extract the first code block from LLM response"""
|
113
|
+
# Pattern to match code blocks with optional language specifier
|
114
|
+
pattern = r'```(?:\w+)?\s*\n(.*?)```'
|
115
|
+
matches = re.findall(pattern, response, re.DOTALL)
|
116
|
+
|
117
|
+
if matches:
|
118
|
+
return matches[0].strip()
|
119
|
+
|
120
|
+
# If no code block found, check if the entire response might be code
|
121
|
+
# (sometimes LLMs return code without backticks)
|
122
|
+
lines = response.strip().split('\n')
|
123
|
+
if len(lines) > 3 and any(line.strip().startswith(('def ', 'class ', 'import ', 'from ')) for line in lines):
|
124
|
+
return response.strip()
|
125
|
+
|
126
|
+
return None
|
127
|
+
|
128
|
+
def expand_existing_code_markers(self, modified_code: str, original_code: str) -> str:
|
129
|
+
"""Expand '# ...existing code...' markers with actual code"""
|
130
|
+
if '# ...existing code...' not in modified_code:
|
131
|
+
return modified_code
|
132
|
+
|
133
|
+
original_lines = original_code.split('\n')
|
134
|
+
modified_lines = modified_code.split('\n')
|
135
|
+
result_lines = []
|
136
|
+
|
137
|
+
original_idx = 0
|
138
|
+
|
139
|
+
for line in modified_lines:
|
140
|
+
if '# ...existing code...' in line.strip():
|
141
|
+
# Skip this marker
|
142
|
+
indent = len(line) - len(line.lstrip())
|
143
|
+
|
144
|
+
# Find the next matching line in modified code
|
145
|
+
next_modified_idx = modified_lines.index(line) + 1
|
146
|
+
next_modified_line = None
|
147
|
+
while next_modified_idx < len(modified_lines):
|
148
|
+
next_line = modified_lines[next_modified_idx]
|
149
|
+
if next_line.strip() and '# ...existing code...' not in next_line:
|
150
|
+
next_modified_line = next_line.strip()
|
151
|
+
break
|
152
|
+
next_modified_idx += 1
|
153
|
+
|
154
|
+
# Copy original lines until we find the next modified line
|
155
|
+
while original_idx < len(original_lines):
|
156
|
+
orig_line = original_lines[original_idx]
|
157
|
+
if next_modified_line and orig_line.strip() == next_modified_line:
|
158
|
+
break
|
159
|
+
result_lines.append(orig_line)
|
160
|
+
original_idx += 1
|
161
|
+
else:
|
162
|
+
result_lines.append(line)
|
163
|
+
# Advance original_idx if this line matches
|
164
|
+
if original_idx < len(original_lines) and line.strip() == original_lines[original_idx].strip():
|
165
|
+
original_idx += 1
|
166
|
+
|
167
|
+
return '\n'.join(result_lines)
|
168
|
+
|
169
|
+
def create_diff(self, original: str, modified: str) -> List[str]:
|
170
|
+
"""Create a unified diff between original and modified code"""
|
171
|
+
original_lines = original.splitlines(keepends=True)
|
172
|
+
modified_lines = modified.splitlines(keepends=True)
|
173
|
+
|
174
|
+
diff = list(difflib.unified_diff(
|
175
|
+
original_lines,
|
176
|
+
modified_lines,
|
177
|
+
fromfile='Original',
|
178
|
+
tofile='Modified',
|
179
|
+
lineterm=''
|
180
|
+
))
|
181
|
+
|
182
|
+
return diff
|
183
|
+
|
184
|
+
def apply_edit(self, editor, new_code: str) -> bool:
|
185
|
+
"""Apply the edit to the editor"""
|
186
|
+
try:
|
187
|
+
text_widget = editor.get_text_widget()
|
188
|
+
|
189
|
+
# Save cursor position
|
190
|
+
cursor_pos = text_widget.index("insert")
|
191
|
+
|
192
|
+
# Replace content
|
193
|
+
text_widget.delete("1.0", tk.END)
|
194
|
+
text_widget.insert("1.0", new_code)
|
195
|
+
|
196
|
+
# Restore cursor position if possible
|
197
|
+
try:
|
198
|
+
text_widget.mark_set("insert", cursor_pos)
|
199
|
+
except:
|
200
|
+
pass
|
201
|
+
|
202
|
+
return True
|
203
|
+
|
204
|
+
except Exception as e:
|
205
|
+
logger.error(f"Failed to apply edit: {e}")
|
206
|
+
return False
|
207
|
+
|
208
|
+
def get_selection_info(self, editor) -> Optional[Tuple[str, int, int]]:
|
209
|
+
"""Get selected text and line numbers if any"""
|
210
|
+
text_widget = editor.get_text_widget()
|
211
|
+
|
212
|
+
if text_widget.tag_ranges("sel"):
|
213
|
+
selected_text = text_widget.get("sel.first", "sel.last")
|
214
|
+
start_line = int(text_widget.index("sel.first").split(".")[0])
|
215
|
+
end_line = int(text_widget.index("sel.last").split(".")[0])
|
216
|
+
return (selected_text, start_line, end_line)
|
217
|
+
|
218
|
+
return None
|
@@ -105,6 +105,9 @@ class LLMChatViewHTML(ttk.Frame):
|
|
105
105
|
|
106
106
|
# 会話履歴を読み込む
|
107
107
|
self._load_chat_history()
|
108
|
+
|
109
|
+
# EditModeHandlerを初期化
|
110
|
+
self.edit_mode_handler = None
|
108
111
|
|
109
112
|
def _show_fallback_ui(self):
|
110
113
|
"""tkinterwebが利用できない場合のフォールバックUI"""
|
@@ -248,6 +251,27 @@ class LLMChatViewHTML(ttk.Frame):
|
|
248
251
|
|
249
252
|
def _create_buttons(self, button_frame):
|
250
253
|
"""ボタン類を作成"""
|
254
|
+
# モード選択フレーム
|
255
|
+
mode_frame = ttk.LabelFrame(button_frame, text=tr("Mode"), padding=2)
|
256
|
+
mode_frame.pack(side=tk.LEFT, padx=5)
|
257
|
+
|
258
|
+
self.mode_var = tk.StringVar(value="chat")
|
259
|
+
ttk.Radiobutton(
|
260
|
+
mode_frame,
|
261
|
+
text=tr("Chat"),
|
262
|
+
variable=self.mode_var,
|
263
|
+
value="chat",
|
264
|
+
command=self._on_mode_change
|
265
|
+
).pack(side=tk.LEFT, padx=2)
|
266
|
+
|
267
|
+
ttk.Radiobutton(
|
268
|
+
mode_frame,
|
269
|
+
text=tr("Edit"),
|
270
|
+
variable=self.mode_var,
|
271
|
+
value="edit",
|
272
|
+
command=self._on_mode_change
|
273
|
+
).pack(side=tk.LEFT, padx=2)
|
274
|
+
|
251
275
|
# Sendボタン
|
252
276
|
self.send_button = ttk.Button(
|
253
277
|
button_frame,
|
@@ -651,6 +675,12 @@ class LLMChatViewHTML(ttk.Frame):
|
|
651
675
|
# UIをクリア
|
652
676
|
self.input_text.delete("1.0", tk.END)
|
653
677
|
|
678
|
+
# Edit modeの場合は特別な処理
|
679
|
+
if self.mode_var.get() == "edit":
|
680
|
+
self._handle_edit_mode(message)
|
681
|
+
return
|
682
|
+
|
683
|
+
# Chat mode(通常の処理)
|
654
684
|
# コンテキスト情報を取得して表示メッセージを作成
|
655
685
|
context_info = self._get_context_info()
|
656
686
|
display_message = self._format_display_message(message, context_info)
|
@@ -913,6 +943,8 @@ Full file content:
|
|
913
943
|
self._handle_token(content)
|
914
944
|
elif msg_type == "complete":
|
915
945
|
self._handle_completion()
|
946
|
+
elif msg_type == "edit_complete":
|
947
|
+
self._handle_edit_completion(content)
|
916
948
|
elif msg_type == "error":
|
917
949
|
self._handle_error(content)
|
918
950
|
elif msg_type == "info":
|
@@ -980,6 +1012,56 @@ Full file content:
|
|
980
1012
|
if self._stop_generation:
|
981
1013
|
self._add_message("system", tr("[Generation stopped by user]"))
|
982
1014
|
|
1015
|
+
def _handle_edit_completion(self, full_response: str):
|
1016
|
+
"""Edit mode完了時の処理"""
|
1017
|
+
# 通常の完了処理
|
1018
|
+
self._handle_completion()
|
1019
|
+
|
1020
|
+
# 中止された場合のチェック
|
1021
|
+
if self._stop_generation:
|
1022
|
+
self._add_message("system", tr("⚠️ Edit generation was stopped. Changes were not applied."))
|
1023
|
+
return
|
1024
|
+
|
1025
|
+
# コードブロックを抽出
|
1026
|
+
new_code = self.edit_mode_handler.extract_code_block(full_response)
|
1027
|
+
|
1028
|
+
if not new_code:
|
1029
|
+
self._add_message("system", tr("No code changes were generated. Please try rephrasing your request."))
|
1030
|
+
return
|
1031
|
+
|
1032
|
+
# エディタを取得
|
1033
|
+
workbench = get_workbench()
|
1034
|
+
editor = workbench.get_editor_notebook().get_current_editor()
|
1035
|
+
if not editor:
|
1036
|
+
self._add_message("system", tr("Editor was closed. Cannot apply changes."))
|
1037
|
+
return
|
1038
|
+
|
1039
|
+
# 現在のコードを取得
|
1040
|
+
text_widget = editor.get_text_widget()
|
1041
|
+
original_code = text_widget.get("1.0", tk.END).strip()
|
1042
|
+
|
1043
|
+
# "# ...existing code..." マーカーを展開
|
1044
|
+
try:
|
1045
|
+
expanded_code = self.edit_mode_handler.expand_existing_code_markers(new_code, original_code)
|
1046
|
+
except Exception as e:
|
1047
|
+
logger.error(f"Failed to expand code markers: {e}")
|
1048
|
+
expanded_code = new_code # フォールバック
|
1049
|
+
|
1050
|
+
# 差分を作成して表示(オプション)
|
1051
|
+
diff_lines = self.edit_mode_handler.create_diff(original_code, expanded_code)
|
1052
|
+
|
1053
|
+
# 変更を適用
|
1054
|
+
if self.edit_mode_handler.apply_edit(editor, expanded_code):
|
1055
|
+
self._add_message("system", tr("✅ Changes applied successfully!"))
|
1056
|
+
|
1057
|
+
# 差分のサマリーを表示
|
1058
|
+
added = sum(1 for line in diff_lines if line.startswith('+') and not line.startswith('+++'))
|
1059
|
+
removed = sum(1 for line in diff_lines if line.startswith('-') and not line.startswith('---'))
|
1060
|
+
if added or removed:
|
1061
|
+
self._add_message("system", f"📊 {added} lines added, {removed} lines removed")
|
1062
|
+
else:
|
1063
|
+
self._add_message("system", tr("❌ Failed to apply changes."))
|
1064
|
+
|
983
1065
|
def _handle_error(self, error_message: str):
|
984
1066
|
"""エラーを処理"""
|
985
1067
|
# ストリーミングエリアを非表示
|
@@ -1194,6 +1276,98 @@ Full file content:
|
|
1194
1276
|
# 履歴もクリア
|
1195
1277
|
self._save_chat_history()
|
1196
1278
|
|
1279
|
+
def _handle_edit_mode(self, user_prompt: str):
|
1280
|
+
"""Edit modeでのメッセージ処理"""
|
1281
|
+
# エディタの情報を取得
|
1282
|
+
workbench = get_workbench()
|
1283
|
+
editor = workbench.get_editor_notebook().get_current_editor()
|
1284
|
+
if not editor:
|
1285
|
+
self._add_message("system", tr("No active editor. Please open a file to edit."))
|
1286
|
+
return
|
1287
|
+
|
1288
|
+
# ファイル情報を取得
|
1289
|
+
filename = editor.get_filename() or "Untitled"
|
1290
|
+
text_widget = editor.get_text_widget()
|
1291
|
+
content = text_widget.get("1.0", tk.END).strip()
|
1292
|
+
|
1293
|
+
if not content:
|
1294
|
+
self._add_message("system", tr("The editor is empty. Please write some code first."))
|
1295
|
+
return
|
1296
|
+
|
1297
|
+
# 選択範囲を取得(あれば)
|
1298
|
+
selection_info = self.edit_mode_handler.get_selection_info(editor)
|
1299
|
+
|
1300
|
+
# メッセージを表示
|
1301
|
+
selection_text = ""
|
1302
|
+
if selection_info:
|
1303
|
+
_, start_line, end_line = selection_info
|
1304
|
+
selection_text = f" (lines {start_line}-{end_line})"
|
1305
|
+
self._add_message("user", f"{user_prompt}\n\n[Edit Mode: {Path(filename).name}{selection_text}]")
|
1306
|
+
|
1307
|
+
# 生成を開始
|
1308
|
+
self._start_edit_generation(user_prompt, filename, content, selection_info)
|
1309
|
+
|
1310
|
+
def _start_edit_generation(self, user_prompt: str, filename: str, content: str, selection_info):
|
1311
|
+
"""Edit mode用の生成を開始"""
|
1312
|
+
if self._processing:
|
1313
|
+
return
|
1314
|
+
|
1315
|
+
self._processing = True
|
1316
|
+
self._stop_generation = False
|
1317
|
+
self.send_button.config(text=tr("Stop"), state=tk.NORMAL) # Stop ボタンを有効化
|
1318
|
+
|
1319
|
+
# ストリーミングの準備
|
1320
|
+
self._start_generating_animation()
|
1321
|
+
|
1322
|
+
# バックグラウンドで生成
|
1323
|
+
def generate():
|
1324
|
+
try:
|
1325
|
+
# Edit用のプロンプトを構築
|
1326
|
+
prompt = self.edit_mode_handler.build_edit_prompt(
|
1327
|
+
user_prompt, filename, content, selection_info
|
1328
|
+
)
|
1329
|
+
|
1330
|
+
# LLMで生成
|
1331
|
+
from .. import get_llm_client
|
1332
|
+
llm_client = get_llm_client()
|
1333
|
+
|
1334
|
+
# 応答を収集
|
1335
|
+
full_response = ""
|
1336
|
+
for token in llm_client.generate_stream(prompt):
|
1337
|
+
if self._stop_generation:
|
1338
|
+
# 中止された場合もedit_completeを送信(部分的な応答で処理)
|
1339
|
+
self.message_queue.put(("edit_complete", full_response))
|
1340
|
+
return
|
1341
|
+
full_response += token
|
1342
|
+
self.message_queue.put(("token", token))
|
1343
|
+
|
1344
|
+
# コードブロックを抽出
|
1345
|
+
self.message_queue.put(("edit_complete", full_response))
|
1346
|
+
|
1347
|
+
except Exception as e:
|
1348
|
+
self.message_queue.put(("error", str(e)))
|
1349
|
+
|
1350
|
+
import threading
|
1351
|
+
thread = threading.Thread(target=generate, daemon=True)
|
1352
|
+
thread.start()
|
1353
|
+
|
1354
|
+
def _on_mode_change(self):
|
1355
|
+
"""モード変更時の処理"""
|
1356
|
+
mode = self.mode_var.get()
|
1357
|
+
if mode == "edit":
|
1358
|
+
# Edit modeに切り替え
|
1359
|
+
self.context_check.config(state=tk.DISABLED)
|
1360
|
+
self.context_var.set(True) # Edit modeでは常にコンテキストを含む
|
1361
|
+
|
1362
|
+
# EditModeHandlerを初期化(必要な場合)
|
1363
|
+
if not self.edit_mode_handler:
|
1364
|
+
from ..edit_mode_handler import EditModeHandler
|
1365
|
+
from .. import get_llm_client
|
1366
|
+
self.edit_mode_handler = EditModeHandler(get_llm_client())
|
1367
|
+
else:
|
1368
|
+
# Chat modeに戻る
|
1369
|
+
self.context_check.config(state=tk.NORMAL)
|
1370
|
+
|
1197
1371
|
def _toggle_context(self):
|
1198
1372
|
"""コンテキストの有効/無効を切り替え"""
|
1199
1373
|
if self.context_var.get():
|
thonnycontrib/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# thonnycontrib namespace package
|
File without changes
|
File without changes
|