chatmcp-cli 0.1.0__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.
- aider/__init__.py +20 -0
- aider/__main__.py +4 -0
- aider/_version.py +21 -0
- aider/analytics.py +250 -0
- aider/args.py +926 -0
- aider/args_formatter.py +228 -0
- aider/coders/__init__.py +34 -0
- aider/coders/architect_coder.py +48 -0
- aider/coders/architect_prompts.py +40 -0
- aider/coders/ask_coder.py +9 -0
- aider/coders/ask_prompts.py +35 -0
- aider/coders/base_coder.py +2483 -0
- aider/coders/base_prompts.py +60 -0
- aider/coders/chat_chunks.py +64 -0
- aider/coders/context_coder.py +53 -0
- aider/coders/context_prompts.py +75 -0
- aider/coders/editblock_coder.py +657 -0
- aider/coders/editblock_fenced_coder.py +10 -0
- aider/coders/editblock_fenced_prompts.py +143 -0
- aider/coders/editblock_func_coder.py +141 -0
- aider/coders/editblock_func_prompts.py +27 -0
- aider/coders/editblock_prompts.py +174 -0
- aider/coders/editor_diff_fenced_coder.py +9 -0
- aider/coders/editor_diff_fenced_prompts.py +11 -0
- aider/coders/editor_editblock_coder.py +8 -0
- aider/coders/editor_editblock_prompts.py +18 -0
- aider/coders/editor_whole_coder.py +8 -0
- aider/coders/editor_whole_prompts.py +10 -0
- aider/coders/help_coder.py +16 -0
- aider/coders/help_prompts.py +46 -0
- aider/coders/patch_coder.py +706 -0
- aider/coders/patch_prompts.py +161 -0
- aider/coders/search_replace.py +757 -0
- aider/coders/shell.py +37 -0
- aider/coders/single_wholefile_func_coder.py +102 -0
- aider/coders/single_wholefile_func_prompts.py +27 -0
- aider/coders/udiff_coder.py +429 -0
- aider/coders/udiff_prompts.py +115 -0
- aider/coders/udiff_simple.py +14 -0
- aider/coders/udiff_simple_prompts.py +25 -0
- aider/coders/wholefile_coder.py +144 -0
- aider/coders/wholefile_func_coder.py +134 -0
- aider/coders/wholefile_func_prompts.py +27 -0
- aider/coders/wholefile_prompts.py +67 -0
- aider/commands.py +1665 -0
- aider/copypaste.py +72 -0
- aider/deprecated.py +126 -0
- aider/diffs.py +128 -0
- aider/dump.py +29 -0
- aider/editor.py +147 -0
- aider/exceptions.py +107 -0
- aider/format_settings.py +26 -0
- aider/gui.py +545 -0
- aider/help.py +163 -0
- aider/help_pats.py +19 -0
- aider/history.py +143 -0
- aider/io.py +1175 -0
- aider/linter.py +304 -0
- aider/llm.py +47 -0
- aider/main.py +1267 -0
- aider/mdstream.py +243 -0
- aider/models.py +1286 -0
- aider/onboarding.py +428 -0
- aider/openrouter.py +128 -0
- aider/prompts.py +64 -0
- aider/queries/tree-sitter-language-pack/README.md +7 -0
- aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- aider/queries/tree-sitter-languages/README.md +23 -0
- aider/queries/tree-sitter-languages/c-tags.scm +9 -0
- aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
- aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
- aider/queries/tree-sitter-languages/go-tags.scm +30 -0
- aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- aider/queries/tree-sitter-languages/java-tags.scm +20 -0
- aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-languages/php-tags.scm +26 -0
- aider/queries/tree-sitter-languages/python-tags.scm +12 -0
- aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
- aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
- aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
- aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- aider/reasoning_tags.py +82 -0
- aider/repo.py +623 -0
- aider/repomap.py +847 -0
- aider/report.py +200 -0
- aider/resources/__init__.py +3 -0
- aider/resources/model-metadata.json +468 -0
- aider/resources/model-settings.yml +1767 -0
- aider/run_cmd.py +132 -0
- aider/scrape.py +284 -0
- aider/sendchat.py +61 -0
- aider/special.py +203 -0
- aider/urls.py +17 -0
- aider/utils.py +338 -0
- aider/versioncheck.py +113 -0
- aider/voice.py +187 -0
- aider/waiting.py +221 -0
- aider/watch.py +318 -0
- aider/watch_prompts.py +12 -0
- aider/website/Gemfile +8 -0
- aider/website/_includes/blame.md +162 -0
- aider/website/_includes/get-started.md +22 -0
- aider/website/_includes/help-tip.md +5 -0
- aider/website/_includes/help.md +24 -0
- aider/website/_includes/install.md +5 -0
- aider/website/_includes/keys.md +4 -0
- aider/website/_includes/model-warnings.md +67 -0
- aider/website/_includes/multi-line.md +22 -0
- aider/website/_includes/python-m-aider.md +5 -0
- aider/website/_includes/recording.css +228 -0
- aider/website/_includes/recording.md +34 -0
- aider/website/_includes/replit-pipx.md +9 -0
- aider/website/_includes/works-best.md +1 -0
- aider/website/_sass/custom/custom.scss +103 -0
- aider/website/docs/config/adv-model-settings.md +1881 -0
- aider/website/docs/config/aider_conf.md +527 -0
- aider/website/docs/config/api-keys.md +90 -0
- aider/website/docs/config/dotenv.md +478 -0
- aider/website/docs/config/editor.md +127 -0
- aider/website/docs/config/model-aliases.md +103 -0
- aider/website/docs/config/options.md +843 -0
- aider/website/docs/config/reasoning.md +209 -0
- aider/website/docs/config.md +44 -0
- aider/website/docs/faq.md +378 -0
- aider/website/docs/git.md +76 -0
- aider/website/docs/index.md +47 -0
- aider/website/docs/install/codespaces.md +39 -0
- aider/website/docs/install/docker.md +57 -0
- aider/website/docs/install/optional.md +100 -0
- aider/website/docs/install/replit.md +8 -0
- aider/website/docs/install.md +115 -0
- aider/website/docs/languages.md +264 -0
- aider/website/docs/legal/contributor-agreement.md +111 -0
- aider/website/docs/legal/privacy.md +104 -0
- aider/website/docs/llms/anthropic.md +77 -0
- aider/website/docs/llms/azure.md +48 -0
- aider/website/docs/llms/bedrock.md +132 -0
- aider/website/docs/llms/cohere.md +34 -0
- aider/website/docs/llms/deepseek.md +32 -0
- aider/website/docs/llms/gemini.md +49 -0
- aider/website/docs/llms/github.md +105 -0
- aider/website/docs/llms/groq.md +36 -0
- aider/website/docs/llms/lm-studio.md +39 -0
- aider/website/docs/llms/ollama.md +75 -0
- aider/website/docs/llms/openai-compat.md +39 -0
- aider/website/docs/llms/openai.md +58 -0
- aider/website/docs/llms/openrouter.md +78 -0
- aider/website/docs/llms/other.md +103 -0
- aider/website/docs/llms/vertex.md +50 -0
- aider/website/docs/llms/warnings.md +10 -0
- aider/website/docs/llms/xai.md +53 -0
- aider/website/docs/llms.md +54 -0
- aider/website/docs/more/analytics.md +122 -0
- aider/website/docs/more/edit-formats.md +116 -0
- aider/website/docs/more/infinite-output.md +137 -0
- aider/website/docs/more-info.md +8 -0
- aider/website/docs/recordings/auto-accept-architect.md +31 -0
- aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
- aider/website/docs/recordings/index.md +21 -0
- aider/website/docs/recordings/model-accepts-settings.md +69 -0
- aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
- aider/website/docs/repomap.md +112 -0
- aider/website/docs/scripting.md +100 -0
- aider/website/docs/troubleshooting/aider-not-found.md +24 -0
- aider/website/docs/troubleshooting/edit-errors.md +76 -0
- aider/website/docs/troubleshooting/imports.md +62 -0
- aider/website/docs/troubleshooting/models-and-keys.md +54 -0
- aider/website/docs/troubleshooting/support.md +79 -0
- aider/website/docs/troubleshooting/token-limits.md +96 -0
- aider/website/docs/troubleshooting/warnings.md +12 -0
- aider/website/docs/troubleshooting.md +11 -0
- aider/website/docs/usage/browser.md +57 -0
- aider/website/docs/usage/caching.md +49 -0
- aider/website/docs/usage/commands.md +132 -0
- aider/website/docs/usage/conventions.md +119 -0
- aider/website/docs/usage/copypaste.md +121 -0
- aider/website/docs/usage/images-urls.md +48 -0
- aider/website/docs/usage/lint-test.md +118 -0
- aider/website/docs/usage/modes.md +211 -0
- aider/website/docs/usage/not-code.md +179 -0
- aider/website/docs/usage/notifications.md +87 -0
- aider/website/docs/usage/tips.md +79 -0
- aider/website/docs/usage/tutorials.md +30 -0
- aider/website/docs/usage/voice.md +121 -0
- aider/website/docs/usage/watch.md +294 -0
- aider/website/docs/usage.md +92 -0
- aider/website/share/index.md +101 -0
- chatmcp_cli-0.1.0.dist-info/METADATA +502 -0
- chatmcp_cli-0.1.0.dist-info/RECORD +228 -0
- chatmcp_cli-0.1.0.dist-info/WHEEL +5 -0
- chatmcp_cli-0.1.0.dist-info/entry_points.txt +3 -0
- chatmcp_cli-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
- chatmcp_cli-0.1.0.dist-info/top_level.txt +1 -0
aider/gui.py
ADDED
@@ -0,0 +1,545 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
import os
|
4
|
+
import random
|
5
|
+
import sys
|
6
|
+
|
7
|
+
import streamlit as st
|
8
|
+
|
9
|
+
from aider import urls
|
10
|
+
from aider.coders import Coder
|
11
|
+
from aider.dump import dump # noqa: F401
|
12
|
+
from aider.io import InputOutput
|
13
|
+
from aider.main import main as cli_main
|
14
|
+
from aider.scrape import Scraper, has_playwright
|
15
|
+
|
16
|
+
|
17
|
+
class CaptureIO(InputOutput):
|
18
|
+
lines = []
|
19
|
+
|
20
|
+
def tool_output(self, msg, log_only=False):
|
21
|
+
if not log_only:
|
22
|
+
self.lines.append(msg)
|
23
|
+
super().tool_output(msg, log_only=log_only)
|
24
|
+
|
25
|
+
def tool_error(self, msg):
|
26
|
+
self.lines.append(msg)
|
27
|
+
super().tool_error(msg)
|
28
|
+
|
29
|
+
def tool_warning(self, msg):
|
30
|
+
self.lines.append(msg)
|
31
|
+
super().tool_warning(msg)
|
32
|
+
|
33
|
+
def get_captured_lines(self):
|
34
|
+
lines = self.lines
|
35
|
+
self.lines = []
|
36
|
+
return lines
|
37
|
+
|
38
|
+
|
39
|
+
def search(text=None):
|
40
|
+
results = []
|
41
|
+
for root, _, files in os.walk("aider"):
|
42
|
+
for file in files:
|
43
|
+
path = os.path.join(root, file)
|
44
|
+
if not text or text in path:
|
45
|
+
results.append(path)
|
46
|
+
# dump(results)
|
47
|
+
|
48
|
+
return results
|
49
|
+
|
50
|
+
|
51
|
+
# Keep state as a resource, which survives browser reloads (since Coder does too)
|
52
|
+
class State:
|
53
|
+
keys = set()
|
54
|
+
|
55
|
+
def init(self, key, val=None):
|
56
|
+
if key in self.keys:
|
57
|
+
return
|
58
|
+
|
59
|
+
self.keys.add(key)
|
60
|
+
setattr(self, key, val)
|
61
|
+
return True
|
62
|
+
|
63
|
+
|
64
|
+
@st.cache_resource
|
65
|
+
def get_state():
|
66
|
+
return State()
|
67
|
+
|
68
|
+
|
69
|
+
@st.cache_resource
|
70
|
+
def get_coder():
|
71
|
+
coder = cli_main(return_coder=True)
|
72
|
+
if not isinstance(coder, Coder):
|
73
|
+
raise ValueError(coder)
|
74
|
+
if not coder.repo:
|
75
|
+
raise ValueError("GUI can currently only be used inside a git repo")
|
76
|
+
|
77
|
+
io = CaptureIO(
|
78
|
+
pretty=False,
|
79
|
+
yes=True,
|
80
|
+
dry_run=coder.io.dry_run,
|
81
|
+
encoding=coder.io.encoding,
|
82
|
+
)
|
83
|
+
# coder.io = io # this breaks the input_history
|
84
|
+
coder.commands.io = io
|
85
|
+
|
86
|
+
for line in coder.get_announcements():
|
87
|
+
coder.io.tool_output(line)
|
88
|
+
|
89
|
+
return coder
|
90
|
+
|
91
|
+
|
92
|
+
class GUI:
|
93
|
+
prompt = None
|
94
|
+
prompt_as = "user"
|
95
|
+
last_undo_empty = None
|
96
|
+
recent_msgs_empty = None
|
97
|
+
web_content_empty = None
|
98
|
+
|
99
|
+
def announce(self):
|
100
|
+
lines = self.coder.get_announcements()
|
101
|
+
lines = " \n".join(lines)
|
102
|
+
return lines
|
103
|
+
|
104
|
+
def show_edit_info(self, edit):
|
105
|
+
commit_hash = edit.get("commit_hash")
|
106
|
+
commit_message = edit.get("commit_message")
|
107
|
+
diff = edit.get("diff")
|
108
|
+
fnames = edit.get("fnames")
|
109
|
+
if fnames:
|
110
|
+
fnames = sorted(fnames)
|
111
|
+
|
112
|
+
if not commit_hash and not fnames:
|
113
|
+
return
|
114
|
+
|
115
|
+
show_undo = False
|
116
|
+
res = ""
|
117
|
+
if commit_hash:
|
118
|
+
res += f"Commit `{commit_hash}`: {commit_message} \n"
|
119
|
+
if commit_hash == self.coder.last_aider_commit_hash:
|
120
|
+
show_undo = True
|
121
|
+
|
122
|
+
if fnames:
|
123
|
+
fnames = [f"`{fname}`" for fname in fnames]
|
124
|
+
fnames = ", ".join(fnames)
|
125
|
+
res += f"Applied edits to {fnames}."
|
126
|
+
|
127
|
+
if diff:
|
128
|
+
with st.expander(res):
|
129
|
+
st.code(diff, language="diff")
|
130
|
+
if show_undo:
|
131
|
+
self.add_undo(commit_hash)
|
132
|
+
else:
|
133
|
+
with st.container(border=True):
|
134
|
+
st.write(res)
|
135
|
+
if show_undo:
|
136
|
+
self.add_undo(commit_hash)
|
137
|
+
|
138
|
+
def add_undo(self, commit_hash):
|
139
|
+
if self.last_undo_empty:
|
140
|
+
self.last_undo_empty.empty()
|
141
|
+
|
142
|
+
self.last_undo_empty = st.empty()
|
143
|
+
undone = self.state.last_undone_commit_hash == commit_hash
|
144
|
+
if not undone:
|
145
|
+
with self.last_undo_empty:
|
146
|
+
if self.button(f"Undo commit `{commit_hash}`", key=f"undo_{commit_hash}"):
|
147
|
+
self.do_undo(commit_hash)
|
148
|
+
|
149
|
+
def do_sidebar(self):
|
150
|
+
with st.sidebar:
|
151
|
+
st.title("Aider")
|
152
|
+
# self.cmds_tab, self.settings_tab = st.tabs(["Commands", "Settings"])
|
153
|
+
|
154
|
+
# self.do_recommended_actions()
|
155
|
+
self.do_add_to_chat()
|
156
|
+
self.do_recent_msgs()
|
157
|
+
self.do_clear_chat_history()
|
158
|
+
# st.container(height=150, border=False)
|
159
|
+
# st.write("### Experimental")
|
160
|
+
|
161
|
+
st.warning(
|
162
|
+
"This browser version of aider is experimental. Please share feedback in [GitHub"
|
163
|
+
" issues](https://github.com/Aider-AI/aider/issues)."
|
164
|
+
)
|
165
|
+
|
166
|
+
def do_settings_tab(self):
|
167
|
+
pass
|
168
|
+
|
169
|
+
def do_recommended_actions(self):
|
170
|
+
text = "Aider works best when your code is stored in a git repo. \n"
|
171
|
+
text += f"[See the FAQ for more info]({urls.git})"
|
172
|
+
|
173
|
+
with st.expander("Recommended actions", expanded=True):
|
174
|
+
with st.popover("Create a git repo to track changes"):
|
175
|
+
st.write(text)
|
176
|
+
self.button("Create git repo", key=random.random(), help="?")
|
177
|
+
|
178
|
+
with st.popover("Update your `.gitignore` file"):
|
179
|
+
st.write("It's best to keep aider's internal files out of your git repo.")
|
180
|
+
self.button("Add `.aider*` to `.gitignore`", key=random.random(), help="?")
|
181
|
+
|
182
|
+
def do_add_to_chat(self):
|
183
|
+
# with st.expander("Add to the chat", expanded=True):
|
184
|
+
self.do_add_files()
|
185
|
+
self.do_add_web_page()
|
186
|
+
|
187
|
+
def do_add_files(self):
|
188
|
+
fnames = st.multiselect(
|
189
|
+
"Add files to the chat",
|
190
|
+
self.coder.get_all_relative_files(),
|
191
|
+
default=self.state.initial_inchat_files,
|
192
|
+
placeholder="Files to edit",
|
193
|
+
disabled=self.prompt_pending(),
|
194
|
+
help=(
|
195
|
+
"Only add the files that need to be *edited* for the task you are working"
|
196
|
+
" on. Aider will pull in other relevant code to provide context to the LLM."
|
197
|
+
),
|
198
|
+
)
|
199
|
+
|
200
|
+
for fname in fnames:
|
201
|
+
if fname not in self.coder.get_inchat_relative_files():
|
202
|
+
self.coder.add_rel_fname(fname)
|
203
|
+
self.info(f"Added {fname} to the chat")
|
204
|
+
|
205
|
+
for fname in self.coder.get_inchat_relative_files():
|
206
|
+
if fname not in fnames:
|
207
|
+
self.coder.drop_rel_fname(fname)
|
208
|
+
self.info(f"Removed {fname} from the chat")
|
209
|
+
|
210
|
+
def do_add_web_page(self):
|
211
|
+
with st.popover("Add a web page to the chat"):
|
212
|
+
self.do_web()
|
213
|
+
|
214
|
+
def do_add_image(self):
|
215
|
+
with st.popover("Add image"):
|
216
|
+
st.markdown("Hello World 👋")
|
217
|
+
st.file_uploader("Image file", disabled=self.prompt_pending())
|
218
|
+
|
219
|
+
def do_run_shell(self):
|
220
|
+
with st.popover("Run shell commands, tests, etc"):
|
221
|
+
st.markdown(
|
222
|
+
"Run a shell command and optionally share the output with the LLM. This is"
|
223
|
+
" a great way to run your program or run tests and have the LLM fix bugs."
|
224
|
+
)
|
225
|
+
st.text_input("Command:")
|
226
|
+
st.radio(
|
227
|
+
"Share the command output with the LLM?",
|
228
|
+
[
|
229
|
+
"Review the output and decide whether to share",
|
230
|
+
"Automatically share the output on non-zero exit code (ie, if any tests fail)",
|
231
|
+
],
|
232
|
+
)
|
233
|
+
st.selectbox(
|
234
|
+
"Recent commands",
|
235
|
+
[
|
236
|
+
"my_app.py --doit",
|
237
|
+
"my_app.py --cleanup",
|
238
|
+
],
|
239
|
+
disabled=self.prompt_pending(),
|
240
|
+
)
|
241
|
+
|
242
|
+
def do_tokens_and_cost(self):
|
243
|
+
with st.expander("Tokens and costs", expanded=True):
|
244
|
+
pass
|
245
|
+
|
246
|
+
def do_show_token_usage(self):
|
247
|
+
with st.popover("Show token usage"):
|
248
|
+
st.write("hi")
|
249
|
+
|
250
|
+
def do_clear_chat_history(self):
|
251
|
+
text = "Saves tokens, reduces confusion"
|
252
|
+
if self.button("Clear chat history", help=text):
|
253
|
+
self.coder.done_messages = []
|
254
|
+
self.coder.cur_messages = []
|
255
|
+
self.info("Cleared chat history. Now the LLM can't see anything before this line.")
|
256
|
+
|
257
|
+
def do_show_metrics(self):
|
258
|
+
st.metric("Cost of last message send & reply", "$0.0019", help="foo")
|
259
|
+
st.metric("Cost to send next message", "$0.0013", help="foo")
|
260
|
+
st.metric("Total cost this session", "$0.22")
|
261
|
+
|
262
|
+
def do_git(self):
|
263
|
+
with st.expander("Git", expanded=False):
|
264
|
+
# st.button("Show last diff")
|
265
|
+
# st.button("Undo last commit")
|
266
|
+
self.button("Commit any pending changes")
|
267
|
+
with st.popover("Run git command"):
|
268
|
+
st.markdown("## Run git command")
|
269
|
+
st.text_input("git", value="git ")
|
270
|
+
self.button("Run")
|
271
|
+
st.selectbox(
|
272
|
+
"Recent git commands",
|
273
|
+
[
|
274
|
+
"git checkout -b experiment",
|
275
|
+
"git stash",
|
276
|
+
],
|
277
|
+
disabled=self.prompt_pending(),
|
278
|
+
)
|
279
|
+
|
280
|
+
def do_recent_msgs(self):
|
281
|
+
if not self.recent_msgs_empty:
|
282
|
+
self.recent_msgs_empty = st.empty()
|
283
|
+
|
284
|
+
if self.prompt_pending():
|
285
|
+
self.recent_msgs_empty.empty()
|
286
|
+
self.state.recent_msgs_num += 1
|
287
|
+
|
288
|
+
with self.recent_msgs_empty:
|
289
|
+
self.old_prompt = st.selectbox(
|
290
|
+
"Resend a recent chat message",
|
291
|
+
self.state.input_history,
|
292
|
+
placeholder="Choose a recent chat message",
|
293
|
+
# label_visibility="collapsed",
|
294
|
+
index=None,
|
295
|
+
key=f"recent_msgs_{self.state.recent_msgs_num}",
|
296
|
+
disabled=self.prompt_pending(),
|
297
|
+
)
|
298
|
+
if self.old_prompt:
|
299
|
+
self.prompt = self.old_prompt
|
300
|
+
|
301
|
+
def do_messages_container(self):
|
302
|
+
self.messages = st.container()
|
303
|
+
|
304
|
+
# stuff a bunch of vertical whitespace at the top
|
305
|
+
# to get all the chat text to the bottom
|
306
|
+
# self.messages.container(height=300, border=False)
|
307
|
+
|
308
|
+
with self.messages:
|
309
|
+
for msg in self.state.messages:
|
310
|
+
role = msg["role"]
|
311
|
+
|
312
|
+
if role == "edit":
|
313
|
+
self.show_edit_info(msg)
|
314
|
+
elif role == "info":
|
315
|
+
st.info(msg["content"])
|
316
|
+
elif role == "text":
|
317
|
+
text = msg["content"]
|
318
|
+
line = text.splitlines()[0]
|
319
|
+
with self.messages.expander(line):
|
320
|
+
st.text(text)
|
321
|
+
elif role in ("user", "assistant"):
|
322
|
+
with st.chat_message(role):
|
323
|
+
st.write(msg["content"])
|
324
|
+
# self.cost()
|
325
|
+
else:
|
326
|
+
st.dict(msg)
|
327
|
+
|
328
|
+
def initialize_state(self):
|
329
|
+
messages = [
|
330
|
+
dict(role="info", content=self.announce()),
|
331
|
+
dict(role="assistant", content="How can I help you?"),
|
332
|
+
]
|
333
|
+
|
334
|
+
self.state.init("messages", messages)
|
335
|
+
self.state.init("last_aider_commit_hash", self.coder.last_aider_commit_hash)
|
336
|
+
self.state.init("last_undone_commit_hash")
|
337
|
+
self.state.init("recent_msgs_num", 0)
|
338
|
+
self.state.init("web_content_num", 0)
|
339
|
+
self.state.init("prompt")
|
340
|
+
self.state.init("scraper")
|
341
|
+
|
342
|
+
self.state.init("initial_inchat_files", self.coder.get_inchat_relative_files())
|
343
|
+
|
344
|
+
if "input_history" not in self.state.keys:
|
345
|
+
input_history = list(self.coder.io.get_input_history())
|
346
|
+
seen = set()
|
347
|
+
input_history = [x for x in input_history if not (x in seen or seen.add(x))]
|
348
|
+
self.state.input_history = input_history
|
349
|
+
self.state.keys.add("input_history")
|
350
|
+
|
351
|
+
def button(self, args, **kwargs):
|
352
|
+
"Create a button, disabled if prompt pending"
|
353
|
+
|
354
|
+
# Force everything to be disabled if there is a prompt pending
|
355
|
+
if self.prompt_pending():
|
356
|
+
kwargs["disabled"] = True
|
357
|
+
|
358
|
+
return st.button(args, **kwargs)
|
359
|
+
|
360
|
+
def __init__(self):
|
361
|
+
self.coder = get_coder()
|
362
|
+
self.state = get_state()
|
363
|
+
|
364
|
+
# Force the coder to cooperate, regardless of cmd line args
|
365
|
+
self.coder.yield_stream = True
|
366
|
+
self.coder.stream = True
|
367
|
+
self.coder.pretty = False
|
368
|
+
|
369
|
+
self.initialize_state()
|
370
|
+
|
371
|
+
self.do_messages_container()
|
372
|
+
self.do_sidebar()
|
373
|
+
|
374
|
+
user_inp = st.chat_input("Say something")
|
375
|
+
if user_inp:
|
376
|
+
self.prompt = user_inp
|
377
|
+
|
378
|
+
if self.prompt_pending():
|
379
|
+
self.process_chat()
|
380
|
+
|
381
|
+
if not self.prompt:
|
382
|
+
return
|
383
|
+
|
384
|
+
self.state.prompt = self.prompt
|
385
|
+
|
386
|
+
if self.prompt_as == "user":
|
387
|
+
self.coder.io.add_to_input_history(self.prompt)
|
388
|
+
|
389
|
+
self.state.input_history.append(self.prompt)
|
390
|
+
|
391
|
+
if self.prompt_as:
|
392
|
+
self.state.messages.append({"role": self.prompt_as, "content": self.prompt})
|
393
|
+
if self.prompt_as == "user":
|
394
|
+
with self.messages.chat_message("user"):
|
395
|
+
st.write(self.prompt)
|
396
|
+
elif self.prompt_as == "text":
|
397
|
+
line = self.prompt.splitlines()[0]
|
398
|
+
line += "??"
|
399
|
+
with self.messages.expander(line):
|
400
|
+
st.text(self.prompt)
|
401
|
+
|
402
|
+
# re-render the UI for the prompt_pending state
|
403
|
+
st.rerun()
|
404
|
+
|
405
|
+
def prompt_pending(self):
|
406
|
+
return self.state.prompt is not None
|
407
|
+
|
408
|
+
def cost(self):
|
409
|
+
cost = random.random() * 0.003 + 0.001
|
410
|
+
st.caption(f"${cost:0.4f}")
|
411
|
+
|
412
|
+
def process_chat(self):
|
413
|
+
prompt = self.state.prompt
|
414
|
+
self.state.prompt = None
|
415
|
+
|
416
|
+
# This duplicates logic from within Coder
|
417
|
+
self.num_reflections = 0
|
418
|
+
self.max_reflections = 3
|
419
|
+
|
420
|
+
while prompt:
|
421
|
+
with self.messages.chat_message("assistant"):
|
422
|
+
res = st.write_stream(self.coder.run_stream(prompt))
|
423
|
+
self.state.messages.append({"role": "assistant", "content": res})
|
424
|
+
# self.cost()
|
425
|
+
|
426
|
+
prompt = None
|
427
|
+
if self.coder.reflected_message:
|
428
|
+
if self.num_reflections < self.max_reflections:
|
429
|
+
self.num_reflections += 1
|
430
|
+
self.info(self.coder.reflected_message)
|
431
|
+
prompt = self.coder.reflected_message
|
432
|
+
|
433
|
+
with self.messages:
|
434
|
+
edit = dict(
|
435
|
+
role="edit",
|
436
|
+
fnames=self.coder.aider_edited_files,
|
437
|
+
)
|
438
|
+
if self.state.last_aider_commit_hash != self.coder.last_aider_commit_hash:
|
439
|
+
edit["commit_hash"] = self.coder.last_aider_commit_hash
|
440
|
+
edit["commit_message"] = self.coder.last_aider_commit_message
|
441
|
+
commits = f"{self.coder.last_aider_commit_hash}~1"
|
442
|
+
diff = self.coder.repo.diff_commits(
|
443
|
+
self.coder.pretty,
|
444
|
+
commits,
|
445
|
+
self.coder.last_aider_commit_hash,
|
446
|
+
)
|
447
|
+
edit["diff"] = diff
|
448
|
+
self.state.last_aider_commit_hash = self.coder.last_aider_commit_hash
|
449
|
+
|
450
|
+
self.state.messages.append(edit)
|
451
|
+
self.show_edit_info(edit)
|
452
|
+
|
453
|
+
# re-render the UI for the non-prompt_pending state
|
454
|
+
st.rerun()
|
455
|
+
|
456
|
+
def info(self, message, echo=True):
|
457
|
+
info = dict(role="info", content=message)
|
458
|
+
self.state.messages.append(info)
|
459
|
+
|
460
|
+
# We will render the tail of the messages array after this call
|
461
|
+
if echo:
|
462
|
+
self.messages.info(message)
|
463
|
+
|
464
|
+
def do_web(self):
|
465
|
+
st.markdown("Add the text content of a web page to the chat")
|
466
|
+
|
467
|
+
if not self.web_content_empty:
|
468
|
+
self.web_content_empty = st.empty()
|
469
|
+
|
470
|
+
if self.prompt_pending():
|
471
|
+
self.web_content_empty.empty()
|
472
|
+
self.state.web_content_num += 1
|
473
|
+
|
474
|
+
with self.web_content_empty:
|
475
|
+
self.web_content = st.text_input(
|
476
|
+
"URL",
|
477
|
+
placeholder="https://...",
|
478
|
+
key=f"web_content_{self.state.web_content_num}",
|
479
|
+
)
|
480
|
+
|
481
|
+
if not self.web_content:
|
482
|
+
return
|
483
|
+
|
484
|
+
url = self.web_content
|
485
|
+
|
486
|
+
if not self.state.scraper:
|
487
|
+
self.scraper = Scraper(print_error=self.info, playwright_available=has_playwright())
|
488
|
+
|
489
|
+
content = self.scraper.scrape(url) or ""
|
490
|
+
if content.strip():
|
491
|
+
content = f"{url}\n\n" + content
|
492
|
+
self.prompt = content
|
493
|
+
self.prompt_as = "text"
|
494
|
+
else:
|
495
|
+
self.info(f"No web content found for `{url}`.")
|
496
|
+
self.web_content = None
|
497
|
+
|
498
|
+
def do_undo(self, commit_hash):
|
499
|
+
self.last_undo_empty.empty()
|
500
|
+
|
501
|
+
if (
|
502
|
+
self.state.last_aider_commit_hash != commit_hash
|
503
|
+
or self.coder.last_aider_commit_hash != commit_hash
|
504
|
+
):
|
505
|
+
self.info(f"Commit `{commit_hash}` is not the latest commit.")
|
506
|
+
return
|
507
|
+
|
508
|
+
self.coder.commands.io.get_captured_lines()
|
509
|
+
reply = self.coder.commands.cmd_undo(None)
|
510
|
+
lines = self.coder.commands.io.get_captured_lines()
|
511
|
+
|
512
|
+
lines = "\n".join(lines)
|
513
|
+
lines = lines.splitlines()
|
514
|
+
lines = " \n".join(lines)
|
515
|
+
self.info(lines, echo=False)
|
516
|
+
|
517
|
+
self.state.last_undone_commit_hash = commit_hash
|
518
|
+
|
519
|
+
if reply:
|
520
|
+
self.prompt_as = None
|
521
|
+
self.prompt = reply
|
522
|
+
|
523
|
+
|
524
|
+
def gui_main():
|
525
|
+
st.set_page_config(
|
526
|
+
layout="wide",
|
527
|
+
page_title="Aider",
|
528
|
+
page_icon=urls.favicon,
|
529
|
+
menu_items={
|
530
|
+
"Get Help": urls.website,
|
531
|
+
"Report a bug": "https://github.com/Aider-AI/aider/issues",
|
532
|
+
"About": "# Aider\nAI pair programming in your browser.",
|
533
|
+
},
|
534
|
+
)
|
535
|
+
|
536
|
+
# config_options = st.config._config_options
|
537
|
+
# for key, value in config_options.items():
|
538
|
+
# print(f"{key}: {value.value}")
|
539
|
+
|
540
|
+
GUI()
|
541
|
+
|
542
|
+
|
543
|
+
if __name__ == "__main__":
|
544
|
+
status = gui_main()
|
545
|
+
sys.exit(status)
|
aider/help.py
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import shutil
|
6
|
+
import warnings
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
import importlib_resources
|
10
|
+
|
11
|
+
from aider import __version__, utils
|
12
|
+
from aider.dump import dump # noqa: F401
|
13
|
+
from aider.help_pats import exclude_website_pats
|
14
|
+
|
15
|
+
warnings.simplefilter("ignore", category=FutureWarning)
|
16
|
+
|
17
|
+
|
18
|
+
def install_help_extra(io):
|
19
|
+
pip_install_cmd = [
|
20
|
+
"aider-chat[help]",
|
21
|
+
"--extra-index-url",
|
22
|
+
"https://download.pytorch.org/whl/cpu",
|
23
|
+
]
|
24
|
+
res = utils.check_pip_install_extra(
|
25
|
+
io,
|
26
|
+
"llama_index.embeddings.huggingface",
|
27
|
+
"To use interactive /help you need to install the help extras",
|
28
|
+
pip_install_cmd,
|
29
|
+
)
|
30
|
+
return res
|
31
|
+
|
32
|
+
|
33
|
+
def get_package_files():
|
34
|
+
for path in importlib_resources.files("aider.website").iterdir():
|
35
|
+
if path.is_file():
|
36
|
+
yield path
|
37
|
+
elif path.is_dir():
|
38
|
+
for subpath in path.rglob("*.md"):
|
39
|
+
yield subpath
|
40
|
+
|
41
|
+
|
42
|
+
def fname_to_url(filepath):
|
43
|
+
website = "website"
|
44
|
+
index = "index.md"
|
45
|
+
md = ".md"
|
46
|
+
|
47
|
+
# Convert backslashes to forward slashes for consistency
|
48
|
+
filepath = filepath.replace("\\", "/")
|
49
|
+
|
50
|
+
# Convert to Path object for easier manipulation
|
51
|
+
path = Path(filepath)
|
52
|
+
|
53
|
+
# Split the path into parts
|
54
|
+
parts = path.parts
|
55
|
+
|
56
|
+
# Find the 'website' part in the path
|
57
|
+
try:
|
58
|
+
website_index = [p.lower() for p in parts].index(website.lower())
|
59
|
+
except ValueError:
|
60
|
+
return "" # 'website' not found in the path
|
61
|
+
|
62
|
+
# Extract the part of the path starting from 'website'
|
63
|
+
relevant_parts = parts[website_index + 1 :]
|
64
|
+
|
65
|
+
# Handle _includes directory
|
66
|
+
if relevant_parts and relevant_parts[0].lower() == "_includes":
|
67
|
+
return ""
|
68
|
+
|
69
|
+
# Join the remaining parts
|
70
|
+
url_path = "/".join(relevant_parts)
|
71
|
+
|
72
|
+
# Handle index.md and other .md files
|
73
|
+
if url_path.lower().endswith(index.lower()):
|
74
|
+
url_path = url_path[: -len(index)]
|
75
|
+
elif url_path.lower().endswith(md.lower()):
|
76
|
+
url_path = url_path[: -len(md)] + ".html"
|
77
|
+
|
78
|
+
# Ensure the URL starts and ends with '/'
|
79
|
+
url_path = url_path.strip("/")
|
80
|
+
|
81
|
+
return f"https://aider.chat/{url_path}"
|
82
|
+
|
83
|
+
|
84
|
+
def get_index():
|
85
|
+
from llama_index.core import (
|
86
|
+
Document,
|
87
|
+
StorageContext,
|
88
|
+
VectorStoreIndex,
|
89
|
+
load_index_from_storage,
|
90
|
+
)
|
91
|
+
from llama_index.core.node_parser import MarkdownNodeParser
|
92
|
+
|
93
|
+
dname = Path.home() / ".aider" / "caches" / ("help." + __version__)
|
94
|
+
|
95
|
+
index = None
|
96
|
+
try:
|
97
|
+
if dname.exists():
|
98
|
+
storage_context = StorageContext.from_defaults(
|
99
|
+
persist_dir=dname,
|
100
|
+
)
|
101
|
+
index = load_index_from_storage(storage_context)
|
102
|
+
except (OSError, json.JSONDecodeError):
|
103
|
+
shutil.rmtree(dname)
|
104
|
+
|
105
|
+
if index is None:
|
106
|
+
parser = MarkdownNodeParser()
|
107
|
+
|
108
|
+
nodes = []
|
109
|
+
for fname in get_package_files():
|
110
|
+
fname = Path(fname)
|
111
|
+
if any(fname.match(pat) for pat in exclude_website_pats):
|
112
|
+
continue
|
113
|
+
|
114
|
+
doc = Document(
|
115
|
+
text=importlib_resources.files("aider.website")
|
116
|
+
.joinpath(fname)
|
117
|
+
.read_text(encoding="utf-8"),
|
118
|
+
metadata=dict(
|
119
|
+
filename=fname.name,
|
120
|
+
extension=fname.suffix,
|
121
|
+
url=fname_to_url(str(fname)),
|
122
|
+
),
|
123
|
+
)
|
124
|
+
nodes += parser.get_nodes_from_documents([doc])
|
125
|
+
|
126
|
+
index = VectorStoreIndex(nodes, show_progress=True)
|
127
|
+
dname.parent.mkdir(parents=True, exist_ok=True)
|
128
|
+
index.storage_context.persist(dname)
|
129
|
+
|
130
|
+
return index
|
131
|
+
|
132
|
+
|
133
|
+
class Help:
|
134
|
+
def __init__(self):
|
135
|
+
from llama_index.core import Settings
|
136
|
+
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
|
137
|
+
|
138
|
+
os.environ["TOKENIZERS_PARALLELISM"] = "true"
|
139
|
+
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")
|
140
|
+
|
141
|
+
index = get_index()
|
142
|
+
|
143
|
+
self.retriever = index.as_retriever(similarity_top_k=20)
|
144
|
+
|
145
|
+
def ask(self, question):
|
146
|
+
nodes = self.retriever.retrieve(question)
|
147
|
+
|
148
|
+
context = f"""# Question: {question}
|
149
|
+
|
150
|
+
# Relevant docs:
|
151
|
+
|
152
|
+
""" # noqa: E231
|
153
|
+
|
154
|
+
for node in nodes:
|
155
|
+
url = node.metadata.get("url", "")
|
156
|
+
if url:
|
157
|
+
url = f' from_url="{url}"'
|
158
|
+
|
159
|
+
context += f"<doc{url}>\n"
|
160
|
+
context += node.text
|
161
|
+
context += "\n</doc>\n\n"
|
162
|
+
|
163
|
+
return context
|