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.
Files changed (228) hide show
  1. aider/__init__.py +20 -0
  2. aider/__main__.py +4 -0
  3. aider/_version.py +21 -0
  4. aider/analytics.py +250 -0
  5. aider/args.py +926 -0
  6. aider/args_formatter.py +228 -0
  7. aider/coders/__init__.py +34 -0
  8. aider/coders/architect_coder.py +48 -0
  9. aider/coders/architect_prompts.py +40 -0
  10. aider/coders/ask_coder.py +9 -0
  11. aider/coders/ask_prompts.py +35 -0
  12. aider/coders/base_coder.py +2483 -0
  13. aider/coders/base_prompts.py +60 -0
  14. aider/coders/chat_chunks.py +64 -0
  15. aider/coders/context_coder.py +53 -0
  16. aider/coders/context_prompts.py +75 -0
  17. aider/coders/editblock_coder.py +657 -0
  18. aider/coders/editblock_fenced_coder.py +10 -0
  19. aider/coders/editblock_fenced_prompts.py +143 -0
  20. aider/coders/editblock_func_coder.py +141 -0
  21. aider/coders/editblock_func_prompts.py +27 -0
  22. aider/coders/editblock_prompts.py +174 -0
  23. aider/coders/editor_diff_fenced_coder.py +9 -0
  24. aider/coders/editor_diff_fenced_prompts.py +11 -0
  25. aider/coders/editor_editblock_coder.py +8 -0
  26. aider/coders/editor_editblock_prompts.py +18 -0
  27. aider/coders/editor_whole_coder.py +8 -0
  28. aider/coders/editor_whole_prompts.py +10 -0
  29. aider/coders/help_coder.py +16 -0
  30. aider/coders/help_prompts.py +46 -0
  31. aider/coders/patch_coder.py +706 -0
  32. aider/coders/patch_prompts.py +161 -0
  33. aider/coders/search_replace.py +757 -0
  34. aider/coders/shell.py +37 -0
  35. aider/coders/single_wholefile_func_coder.py +102 -0
  36. aider/coders/single_wholefile_func_prompts.py +27 -0
  37. aider/coders/udiff_coder.py +429 -0
  38. aider/coders/udiff_prompts.py +115 -0
  39. aider/coders/udiff_simple.py +14 -0
  40. aider/coders/udiff_simple_prompts.py +25 -0
  41. aider/coders/wholefile_coder.py +144 -0
  42. aider/coders/wholefile_func_coder.py +134 -0
  43. aider/coders/wholefile_func_prompts.py +27 -0
  44. aider/coders/wholefile_prompts.py +67 -0
  45. aider/commands.py +1665 -0
  46. aider/copypaste.py +72 -0
  47. aider/deprecated.py +126 -0
  48. aider/diffs.py +128 -0
  49. aider/dump.py +29 -0
  50. aider/editor.py +147 -0
  51. aider/exceptions.py +107 -0
  52. aider/format_settings.py +26 -0
  53. aider/gui.py +545 -0
  54. aider/help.py +163 -0
  55. aider/help_pats.py +19 -0
  56. aider/history.py +143 -0
  57. aider/io.py +1175 -0
  58. aider/linter.py +304 -0
  59. aider/llm.py +47 -0
  60. aider/main.py +1267 -0
  61. aider/mdstream.py +243 -0
  62. aider/models.py +1286 -0
  63. aider/onboarding.py +428 -0
  64. aider/openrouter.py +128 -0
  65. aider/prompts.py +64 -0
  66. aider/queries/tree-sitter-language-pack/README.md +7 -0
  67. aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  68. aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  69. aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  70. aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  71. aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  72. aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  73. aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  74. aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  75. aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  76. aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  77. aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  78. aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  79. aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  80. aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  81. aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  82. aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  83. aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  84. aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  85. aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  86. aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  87. aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  88. aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  89. aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  90. aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  91. aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  92. aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  93. aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  94. aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  95. aider/queries/tree-sitter-languages/README.md +23 -0
  96. aider/queries/tree-sitter-languages/c-tags.scm +9 -0
  97. aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  98. aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  99. aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
  100. aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  101. aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  102. aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
  103. aider/queries/tree-sitter-languages/go-tags.scm +30 -0
  104. aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  105. aider/queries/tree-sitter-languages/java-tags.scm +20 -0
  106. aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  107. aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  108. aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  109. aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  110. aider/queries/tree-sitter-languages/php-tags.scm +26 -0
  111. aider/queries/tree-sitter-languages/python-tags.scm +12 -0
  112. aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
  113. aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  114. aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
  115. aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
  116. aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  117. aider/reasoning_tags.py +82 -0
  118. aider/repo.py +623 -0
  119. aider/repomap.py +847 -0
  120. aider/report.py +200 -0
  121. aider/resources/__init__.py +3 -0
  122. aider/resources/model-metadata.json +468 -0
  123. aider/resources/model-settings.yml +1767 -0
  124. aider/run_cmd.py +132 -0
  125. aider/scrape.py +284 -0
  126. aider/sendchat.py +61 -0
  127. aider/special.py +203 -0
  128. aider/urls.py +17 -0
  129. aider/utils.py +338 -0
  130. aider/versioncheck.py +113 -0
  131. aider/voice.py +187 -0
  132. aider/waiting.py +221 -0
  133. aider/watch.py +318 -0
  134. aider/watch_prompts.py +12 -0
  135. aider/website/Gemfile +8 -0
  136. aider/website/_includes/blame.md +162 -0
  137. aider/website/_includes/get-started.md +22 -0
  138. aider/website/_includes/help-tip.md +5 -0
  139. aider/website/_includes/help.md +24 -0
  140. aider/website/_includes/install.md +5 -0
  141. aider/website/_includes/keys.md +4 -0
  142. aider/website/_includes/model-warnings.md +67 -0
  143. aider/website/_includes/multi-line.md +22 -0
  144. aider/website/_includes/python-m-aider.md +5 -0
  145. aider/website/_includes/recording.css +228 -0
  146. aider/website/_includes/recording.md +34 -0
  147. aider/website/_includes/replit-pipx.md +9 -0
  148. aider/website/_includes/works-best.md +1 -0
  149. aider/website/_sass/custom/custom.scss +103 -0
  150. aider/website/docs/config/adv-model-settings.md +1881 -0
  151. aider/website/docs/config/aider_conf.md +527 -0
  152. aider/website/docs/config/api-keys.md +90 -0
  153. aider/website/docs/config/dotenv.md +478 -0
  154. aider/website/docs/config/editor.md +127 -0
  155. aider/website/docs/config/model-aliases.md +103 -0
  156. aider/website/docs/config/options.md +843 -0
  157. aider/website/docs/config/reasoning.md +209 -0
  158. aider/website/docs/config.md +44 -0
  159. aider/website/docs/faq.md +378 -0
  160. aider/website/docs/git.md +76 -0
  161. aider/website/docs/index.md +47 -0
  162. aider/website/docs/install/codespaces.md +39 -0
  163. aider/website/docs/install/docker.md +57 -0
  164. aider/website/docs/install/optional.md +100 -0
  165. aider/website/docs/install/replit.md +8 -0
  166. aider/website/docs/install.md +115 -0
  167. aider/website/docs/languages.md +264 -0
  168. aider/website/docs/legal/contributor-agreement.md +111 -0
  169. aider/website/docs/legal/privacy.md +104 -0
  170. aider/website/docs/llms/anthropic.md +77 -0
  171. aider/website/docs/llms/azure.md +48 -0
  172. aider/website/docs/llms/bedrock.md +132 -0
  173. aider/website/docs/llms/cohere.md +34 -0
  174. aider/website/docs/llms/deepseek.md +32 -0
  175. aider/website/docs/llms/gemini.md +49 -0
  176. aider/website/docs/llms/github.md +105 -0
  177. aider/website/docs/llms/groq.md +36 -0
  178. aider/website/docs/llms/lm-studio.md +39 -0
  179. aider/website/docs/llms/ollama.md +75 -0
  180. aider/website/docs/llms/openai-compat.md +39 -0
  181. aider/website/docs/llms/openai.md +58 -0
  182. aider/website/docs/llms/openrouter.md +78 -0
  183. aider/website/docs/llms/other.md +103 -0
  184. aider/website/docs/llms/vertex.md +50 -0
  185. aider/website/docs/llms/warnings.md +10 -0
  186. aider/website/docs/llms/xai.md +53 -0
  187. aider/website/docs/llms.md +54 -0
  188. aider/website/docs/more/analytics.md +122 -0
  189. aider/website/docs/more/edit-formats.md +116 -0
  190. aider/website/docs/more/infinite-output.md +137 -0
  191. aider/website/docs/more-info.md +8 -0
  192. aider/website/docs/recordings/auto-accept-architect.md +31 -0
  193. aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
  194. aider/website/docs/recordings/index.md +21 -0
  195. aider/website/docs/recordings/model-accepts-settings.md +69 -0
  196. aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
  197. aider/website/docs/repomap.md +112 -0
  198. aider/website/docs/scripting.md +100 -0
  199. aider/website/docs/troubleshooting/aider-not-found.md +24 -0
  200. aider/website/docs/troubleshooting/edit-errors.md +76 -0
  201. aider/website/docs/troubleshooting/imports.md +62 -0
  202. aider/website/docs/troubleshooting/models-and-keys.md +54 -0
  203. aider/website/docs/troubleshooting/support.md +79 -0
  204. aider/website/docs/troubleshooting/token-limits.md +96 -0
  205. aider/website/docs/troubleshooting/warnings.md +12 -0
  206. aider/website/docs/troubleshooting.md +11 -0
  207. aider/website/docs/usage/browser.md +57 -0
  208. aider/website/docs/usage/caching.md +49 -0
  209. aider/website/docs/usage/commands.md +132 -0
  210. aider/website/docs/usage/conventions.md +119 -0
  211. aider/website/docs/usage/copypaste.md +121 -0
  212. aider/website/docs/usage/images-urls.md +48 -0
  213. aider/website/docs/usage/lint-test.md +118 -0
  214. aider/website/docs/usage/modes.md +211 -0
  215. aider/website/docs/usage/not-code.md +179 -0
  216. aider/website/docs/usage/notifications.md +87 -0
  217. aider/website/docs/usage/tips.md +79 -0
  218. aider/website/docs/usage/tutorials.md +30 -0
  219. aider/website/docs/usage/voice.md +121 -0
  220. aider/website/docs/usage/watch.md +294 -0
  221. aider/website/docs/usage.md +92 -0
  222. aider/website/share/index.md +101 -0
  223. chatmcp_cli-0.1.0.dist-info/METADATA +502 -0
  224. chatmcp_cli-0.1.0.dist-info/RECORD +228 -0
  225. chatmcp_cli-0.1.0.dist-info/WHEEL +5 -0
  226. chatmcp_cli-0.1.0.dist-info/entry_points.txt +3 -0
  227. chatmcp_cli-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
  228. 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