jarvis-ai-assistant 0.1.88__py3-none-any.whl → 0.1.90__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.

Potentially problematic release.


This version of jarvis-ai-assistant might be problematic. Click here for more details.

@@ -1,706 +0,0 @@
1
- import os
2
- import re
3
- import threading
4
- import time
5
- from typing import Dict, Any, List, Tuple
6
-
7
- import yaml
8
- from jarvis.models.base import BasePlatform
9
- from jarvis.utils import OutputType, PrettyOutput, find_git_root, get_max_context_length, get_multiline_input, load_env_from_file
10
- from jarvis.models.registry import PlatformRegistry
11
- from jarvis.jarvis_codebase.main import CodeBase
12
- from prompt_toolkit import PromptSession
13
- from prompt_toolkit.completion import WordCompleter, Completer, Completion
14
- from prompt_toolkit.formatted_text import FormattedText
15
- from prompt_toolkit.styles import Style
16
- import fnmatch
17
-
18
- # 全局锁对象
19
- index_lock = threading.Lock()
20
-
21
- class JarvisCoder:
22
- def __init__(self, root_dir: str, language: str):
23
- """初始化代码修改工具"""
24
-
25
- self.max_context_length = get_max_context_length()
26
-
27
-
28
- self.root_dir = find_git_root(root_dir)
29
- if not self.root_dir:
30
- self.root_dir = root_dir
31
-
32
- PrettyOutput.print(f"Git根目录: {self.root_dir}", OutputType.INFO)
33
-
34
- # 1. 判断代码库路径是否存在,如果不存在,创建
35
- if not os.path.exists(self.root_dir):
36
- PrettyOutput.print(
37
- "Root directory does not exist, creating...", OutputType.INFO)
38
- os.makedirs(self.root_dir)
39
-
40
- os.chdir(self.root_dir)
41
-
42
- self.jarvis_dir = os.path.join(self.root_dir, ".jarvis-coder")
43
- if not os.path.exists(self.jarvis_dir):
44
- os.makedirs(self.jarvis_dir)
45
-
46
- self.record_dir = os.path.join(self.jarvis_dir, "record")
47
- if not os.path.exists(self.record_dir):
48
- os.makedirs(self.record_dir)
49
-
50
- # 2. 判断代码库是否是git仓库,如果不是,初始化git仓库
51
- if not os.path.exists(os.path.join(self.root_dir, ".git")):
52
- PrettyOutput.print(
53
- "Git repository does not exist, initializing...", OutputType.INFO)
54
- os.system(f"git init")
55
- # 2.1 添加所有的文件
56
- os.system(f"git add .")
57
- # 2.2 提交
58
- os.system(f"git commit -m 'Initial commit'")
59
-
60
- # 3. 查看代码库是否有未提交的文件,如果有,提交一次
61
- if self._has_uncommitted_files():
62
- PrettyOutput.print("代码库有未提交的文件,提交一次", OutputType.INFO)
63
- os.system(f"git add .")
64
- os.system(f"git commit -m 'commit before code edit'")
65
-
66
- # 4. 初始化代码库
67
- self._codebase = CodeBase(self.root_dir)
68
-
69
- def _new_model(self):
70
- """获取大模型"""
71
- model = PlatformRegistry().get_global_platform_registry().get_codegen_platform()
72
- return model
73
-
74
- def _has_uncommitted_files(self) -> bool:
75
- """判断代码库是否有未提交的文件"""
76
- # 获取未暂存的修改
77
- unstaged = os.popen("git diff --name-only").read()
78
- # 获取已暂存但未提交的修改
79
- staged = os.popen("git diff --cached --name-only").read()
80
- # 获取未跟踪的文件
81
- untracked = os.popen("git ls-files --others --exclude-standard").read()
82
-
83
- return bool(unstaged or staged or untracked)
84
-
85
- def _call_model_with_retry(self, model: BasePlatform, prompt: str, max_retries: int = 3, initial_delay: float = 1.0) -> Tuple[bool, str]:
86
- """调用模型并支持重试
87
-
88
- Args:
89
- prompt: 提示词
90
- max_retries: 最大重试次数
91
- initial_delay: 初始延迟时间(秒)
92
-
93
- Returns:
94
- Tuple[bool, str]: (是否成功, 响应内容)
95
- """
96
- delay = initial_delay
97
- for attempt in range(max_retries):
98
- try:
99
- response = model.chat(prompt)
100
- return True, response
101
- except Exception as e:
102
- if attempt == max_retries - 1: # 最后一次尝试
103
- PrettyOutput.print(f"调用模型失败: {str(e)}", OutputType.ERROR)
104
- return False, str(e)
105
-
106
- PrettyOutput.print(f"调用模型失败,{delay}秒后重试: {str(e)}", OutputType.WARNING)
107
- time.sleep(delay)
108
- delay *= 2 # 指数退避
109
-
110
- def _remake_patch(self, prompt: str) -> List[str]:
111
- success, response = self._call_model_with_retry(self.main_model, prompt, max_retries=5) # 增加重试次数
112
- if not success:
113
- return []
114
-
115
- try:
116
- patches = re.findall(r'<PATCH>.*?</PATCH>', response, re.DOTALL)
117
- return [patch.replace('<PATCH>', '').replace('</PATCH>', '').strip()
118
- for patch in patches if patch.strip()]
119
- except Exception as e:
120
- PrettyOutput.print(f"解析patch失败: {str(e)}", OutputType.WARNING)
121
- return []
122
-
123
- def _make_patch(self, related_files: List[Dict], feature: str) -> List[str]:
124
- """生成修改方案"""
125
- prompt = """你是一个资深程序员,请根据需求描述,修改文件内容。
126
-
127
- 修改格式说明:
128
- 1. 每个修改块格式如下:
129
- <PATCH>
130
- > path/to/file
131
- def old_function():
132
- print("old code")
133
- return False
134
- =======
135
- def old_function():
136
- print("new code")
137
- return True
138
- </PATCH>
139
-
140
- 2. 如果是新文件或者替换整个文件内容,格式如下:
141
- <PATCH>
142
- > src/new_module.py
143
- =======
144
- from typing import List
145
-
146
- def new_function():
147
- return "This is a new file"
148
- </PATCH>
149
-
150
- 3. 如果要删除文件中的某一段,格式如下:
151
- <PATCH>
152
- > path/to/file
153
- # 这是要删除的注释
154
- deprecated_code = True
155
- if deprecated_code:
156
- print("old feature")
157
- =======
158
- </PATCH>
159
-
160
- 4. 如果要修改导入语句,格式如下:
161
- <PATCH>
162
- > src/main.py
163
- from old_module import old_class
164
- =======
165
- from new_module import new_class
166
- </PATCH>
167
-
168
- 5. 如果要修改类定义,格式如下:
169
- <PATCH>
170
- > src/models.py
171
- class OldModel:
172
- def __init__(self):
173
- self.value = 0
174
- =======
175
- class OldModel:
176
- def __init__(self):
177
- self.value = 1
178
- self.name = "new"
179
- </PATCH>
180
-
181
- 文件列表如下:
182
- """
183
- for i, file in enumerate(related_files):
184
- if len(prompt) > self.max_context_length:
185
- PrettyOutput.print(f'避免上下文超限,丢弃低相关度文件:{file["file_path"]}', OutputType.WARNING)
186
- continue
187
- prompt += f"""{i}. {file["file_path"]}\n"""
188
- prompt += f"""文件内容:\n"""
189
- prompt += f"<FILE_CONTENT>\n"
190
- prompt += f'{file["file_content"]}\n'
191
- prompt += f"</FILE_CONTENT>\n"
192
-
193
- prompt += f"\n需求描述: {feature}\n"
194
- prompt += """
195
- 注意事项:
196
- 1、仅输出补丁内容,不要输出任何其他内容,每个补丁必须用<PATCH>和</PATCH>标记
197
- 2、如果在大段代码中有零星修改,生成多个补丁
198
- 3、要替换的内容,一定要与文件内容完全一致,不要有任何多余或者缺失的内容
199
- 4、每个patch不超过20行,超出20行,请生成多个patch
200
- """
201
-
202
- success, response = self._call_model_with_retry(self.main_model, prompt)
203
- if not success:
204
- return []
205
-
206
- try:
207
- # 使用正则表达式匹配每个patch块
208
- patches = re.findall(r'<PATCH>.*?</PATCH>', response, re.DOTALL)
209
- return [patch.replace('<PATCH>', '').replace('</PATCH>', '').strip()
210
- for patch in patches if patch.strip()]
211
- except Exception as e:
212
- PrettyOutput.print(f"解析patch失败: {str(e)}", OutputType.WARNING)
213
- return []
214
-
215
- def _apply_patch(self, related_files: List[Dict], patches: List[str]) -> Tuple[bool, str]:
216
- """应用补丁"""
217
- error_info = []
218
- modified_files = set()
219
-
220
- # 创建文件内容映射
221
- file_map = {file["file_path"]: file["file_content"] for file in related_files}
222
- temp_map = file_map.copy() # 创建临时映射用于尝试应用
223
-
224
- # 尝试应用所有补丁
225
- for i, patch in enumerate(patches):
226
- PrettyOutput.print(f"正在应用补丁 {i+1}/{len(patches)}", OutputType.INFO)
227
-
228
- try:
229
- # 解析补丁
230
- lines = patch.split("\n")
231
- if not lines:
232
- continue
233
-
234
- # 获取文件路径
235
- file_path_match = re.search(r'> (.*)', lines[0])
236
- if not file_path_match:
237
- error_info.append(f"无法解析文件路径: {lines[0]}")
238
- return False, "\n".join(error_info)
239
-
240
- file_path = file_path_match.group(1).strip()
241
-
242
- # 解析补丁内容
243
- patch_content = "\n".join(lines[1:])
244
- parts = patch_content.split("=======")
245
-
246
- if len(parts) != 2:
247
- error_info.append(f"补丁格式错误: {file_path}")
248
- return False, "\n".join(error_info)
249
-
250
- old_content = parts[0]
251
- new_content = parts[1].split("</PATCH>")[0]
252
-
253
- # 处理新文件
254
- if not old_content:
255
- temp_map[file_path] = new_content
256
- modified_files.add(file_path)
257
- continue
258
-
259
- # 处理文件修改
260
- if file_path not in temp_map:
261
- error_info.append(f"文件不存在: {file_path}")
262
- return False, "\n".join(error_info)
263
-
264
- current_content = temp_map[file_path]
265
-
266
- # 查找并替换代码块
267
- if old_content not in current_content:
268
- error_info.append(
269
- f"补丁应用失败: {file_path}\n"
270
- f"原因: 未找到要替换的代码\n"
271
- f"期望找到的代码:\n{old_content}\n"
272
- f"实际文件内容:\n{current_content[:200]}..." # 只显示前200个字符
273
- )
274
- return False, "\n".join(error_info)
275
-
276
- # 应用更改
277
- temp_map[file_path] = current_content.replace(old_content, new_content)
278
- modified_files.add(file_path)
279
-
280
- except Exception as e:
281
- error_info.append(f"处理补丁时发生错误: {str(e)}")
282
- return False, "\n".join(error_info)
283
-
284
- # 所有补丁都应用成功,更新实际文件
285
- for file_path in modified_files:
286
- try:
287
- dir_path = os.path.dirname(file_path)
288
- if dir_path and not os.path.exists(dir_path):
289
- os.makedirs(dir_path, exist_ok=True)
290
-
291
- with open(file_path, "w", encoding="utf-8") as f:
292
- f.write(temp_map[file_path])
293
-
294
- PrettyOutput.print(f"成功修改文件: {file_path}", OutputType.SUCCESS)
295
-
296
- except Exception as e:
297
- error_info.append(f"写入文件失败 {file_path}: {str(e)}")
298
- return False, "\n".join(error_info)
299
-
300
- return True, ""
301
-
302
- def _save_edit_record(self, commit_message: str, git_diff: str) -> None:
303
- """保存代码修改记录
304
-
305
- Args:
306
- commit_message: 提交信息
307
- git_diff: git diff --cached的输出
308
- """
309
-
310
- # 获取下一个序号
311
- existing_records = [f for f in os.listdir(self.record_dir) if f.endswith('.yaml')]
312
- next_num = 1
313
- if existing_records:
314
- last_num = max(int(f[:4]) for f in existing_records)
315
- next_num = last_num + 1
316
-
317
- # 创建记录文件
318
- record = {
319
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
320
- "commit_message": commit_message,
321
- "git_diff": git_diff
322
- }
323
-
324
- record_path = os.path.join(self.record_dir, f"{next_num:04d}.yaml")
325
- with open(record_path, "w", encoding="utf-8") as f:
326
- yaml.safe_dump(record, f, allow_unicode=True)
327
-
328
- PrettyOutput.print(f"已保存修改记录: {record_path}", OutputType.SUCCESS)
329
-
330
-
331
-
332
-
333
- def _prepare_execution(self) -> None:
334
- """准备执行环境"""
335
- self.main_model = self._new_model()
336
- self._codebase.generate_codebase()
337
-
338
-
339
- def _load_related_files(self, feature: str) -> List[Dict]:
340
- """加载相关文件内容"""
341
- ret = []
342
- # 确保索引数据库已生成
343
- if not self._codebase.is_index_generated():
344
- PrettyOutput.print("检测到索引数据库未生成,正在生成...", OutputType.WARNING)
345
- self._codebase.generate_codebase()
346
-
347
- related_files = self._codebase.search_similar(feature)
348
- for file, score, _ in related_files:
349
- PrettyOutput.print(f"相关文件: {file} 相关度: {score:.3f}", OutputType.SUCCESS)
350
- with open(file, "r", encoding="utf-8") as f:
351
- content = f.read()
352
- ret.append({"file_path": file, "file_content": content})
353
- return ret
354
-
355
- def _handle_patch_application(self, related_files: List[Dict], patches: List[str], feature: str) -> Dict[str, Any]:
356
- """处理补丁应用流程"""
357
- while True:
358
- PrettyOutput.print(f"生成{len(patches)}个补丁", OutputType.INFO)
359
-
360
- if not patches:
361
- retry_prompt = f"""未生成补丁,请重新生成补丁"""
362
- patches = self._remake_patch(retry_prompt)
363
- continue
364
-
365
- success, error_info = self._apply_patch(related_files, patches)
366
-
367
- if success:
368
- user_confirm = input("是否确认修改?(y/n)")
369
- if user_confirm.lower() == "y":
370
- self._finalize_changes(feature)
371
- return {
372
- "success": True,
373
- "stdout": f"已完成功能开发{feature}",
374
- "stderr": "",
375
- "error": None
376
- }
377
- else:
378
- self._revert_changes()
379
-
380
- # 让用户输入调整意见
381
- user_feedback = get_multiline_input("""
382
- 请提供修改建议,帮助生成更好的补丁:
383
- 1. 修改的位置是否正确?
384
- 2. 修改的内容是否合适?
385
- 3. 是否有遗漏的修改?
386
- 4. 其他调整建议?
387
-
388
- 请输入调整意见(直接回车跳过):""")
389
-
390
- if not user_feedback:
391
- return {
392
- "success": False,
393
- "stdout": "",
394
- "stderr": "修改被用户取消,文件未发生任何变化",
395
- "error": UserWarning("用户取消修改")
396
- }
397
-
398
- retry_prompt = f"""补丁被用户拒绝,请根据用户意见重新生成补丁:
399
-
400
- 用户意见:
401
- {user_feedback}
402
-
403
- 请重新生成补丁,确保:
404
- 1. 按照用户意见调整修改内容
405
- 2. 准确定位要修改的代码位置
406
- 3. 正确处理代码缩进
407
- 4. 考虑代码上下文
408
- """
409
- patches = self._remake_patch(retry_prompt)
410
- continue
411
- else:
412
- PrettyOutput.print(f"补丁应用失败: {error_info}", OutputType.WARNING)
413
-
414
- # 让用户输入补充信息
415
- user_info = get_multiline_input("""
416
- 补丁应用失败。请提供更多信息来帮助修复问题:
417
- 1. 是否需要调整代码位置?
418
- 2. 是否有特殊的格式要求?
419
- 3. 是否需要考虑其他文件的依赖?
420
- 4. 其他补充说明?
421
-
422
- 请输入补充信息(直接回车跳过):""")
423
-
424
- retry_prompt = f"""补丁应用失败,请根据以下信息重新生成补丁:
425
-
426
- 错误信息:
427
- {error_info}
428
-
429
- 用户补充信息:
430
- {user_info if user_info else "用户未提供补充信息"}
431
-
432
- 请确保:
433
- 1. 准确定位要修改的代码位置
434
- 2. 正确处理代码缩进
435
- 3. 考虑代码上下文
436
- 4. 对新文件不要包含原始内容
437
- """
438
- patches = self._remake_patch(retry_prompt)
439
-
440
-
441
-
442
-
443
- def _generate_commit_message(self, git_diff: str, feature: str) -> str:
444
- """根据git diff和功能描述生成commit信息
445
-
446
- Args:
447
- git_diff: git diff --cached的输出
448
- feature: 用户的功能描述
449
-
450
- Returns:
451
- str: 生成的commit信息
452
- """
453
-
454
- # 生成提示词
455
- prompt = f"""你是一个经验丰富的程序员,请根据以下代码变更和功能描述生成简洁明了的commit信息:
456
-
457
- 功能描述:
458
- {feature}
459
-
460
- 代码变更:
461
- """
462
- # 添加git diff内容
463
- prompt += f"Git Diff:\n{git_diff}\n\n"
464
-
465
- prompt += """
466
- 请遵循以下规则:
467
- 1. 使用英文编写
468
- 2. 采用常规的commit message格式:<type>(<scope>): <subject>
469
- 3. 保持简洁,不超过50个字符
470
- 4. 准确描述代码变更的主要内容
471
- 5. 优先考虑功能描述和git diff中的变更内容
472
- """
473
-
474
- # 使用normal模型生成commit信息
475
- model = PlatformRegistry().get_global_platform_registry().get_codegen_platform()
476
- model.set_suppress_output(True)
477
- success, response = self._call_model_with_retry(model, prompt)
478
- if not success:
479
- return "Update code changes"
480
-
481
- # 清理响应内容
482
- return response.strip().split("\n")[0]
483
-
484
- def _finalize_changes(self, feature: str) -> None:
485
- """完成修改并提交"""
486
- PrettyOutput.print("修改确认成功,提交修改", OutputType.INFO)
487
-
488
- # 只添加已经在 git 控制下的修改文件
489
- os.system("git add -u")
490
-
491
- # 然后获取 git diff
492
- git_diff = os.popen("git diff --cached").read()
493
-
494
- # 自动生成commit信息,传入feature
495
- commit_message = self._generate_commit_message(git_diff, feature)
496
-
497
- # 显示并确认commit信息
498
- PrettyOutput.print(f"自动生成的commit信息: {commit_message}", OutputType.INFO)
499
- user_confirm = input("是否使用该commit信息?(y/n) [y]: ") or "y"
500
-
501
- if user_confirm.lower() != "y":
502
- commit_message = input("请输入新的commit信息: ")
503
-
504
- # 不需要再次 git add,因为已经添加过了
505
- os.system(f"git commit -m '{commit_message}'")
506
- self._save_edit_record(commit_message, git_diff)
507
-
508
- def _revert_changes(self) -> None:
509
- """回退所有修改"""
510
- PrettyOutput.print("修改已取消,回退更改", OutputType.INFO)
511
- os.system(f"git reset --hard")
512
- os.system(f"git clean -df")
513
-
514
- def execute(self, feature: str) -> Dict[str, Any]:
515
- """执行代码修改
516
-
517
- Args:
518
- feature: 要实现的功能描述
519
-
520
- Returns:
521
- Dict[str, Any]: 包含执行结果的字典
522
- - success: 是否成功
523
- - stdout: 标准输出信息
524
- - stderr: 错误信息
525
- - error: 错误对象(如果有)
526
- """
527
- try:
528
- self._prepare_execution()
529
- related_files = self._load_related_files(feature)
530
- patches = self._make_patch(related_files, feature)
531
- return self._handle_patch_application(related_files, patches, feature)
532
-
533
- except Exception as e:
534
- return {
535
- "success": False,
536
- "stdout": "",
537
- "stderr": f"执行失败: {str(e)}",
538
- "error": e
539
- }
540
-
541
-
542
- def main():
543
- """命令行入口"""
544
- import argparse
545
-
546
- load_env_from_file()
547
-
548
- parser = argparse.ArgumentParser(description='代码修改工具')
549
- parser.add_argument('-d', '--dir', help='项目根目录', default=os.getcwd())
550
- parser.add_argument('-l', '--language', help='编程语言', default="python")
551
- args = parser.parse_args()
552
-
553
-
554
- tool = JarvisCoder(args.dir, args.language)
555
-
556
- # 循环处理需求
557
- while True:
558
- try:
559
- # 获取需求,传入项目根目录
560
- feature = get_multiline_input("请输入开发需求 (输入空行退出):", tool.root_dir)
561
-
562
- if not feature or feature == "__interrupt__":
563
- break
564
-
565
- # 执行修改
566
- result = tool.execute(feature)
567
-
568
- # 显示结果
569
- if result["success"]:
570
- PrettyOutput.print(result["stdout"], OutputType.SUCCESS)
571
- else:
572
- if result["stderr"]:
573
- PrettyOutput.print(result["stderr"], OutputType.WARNING)
574
- if result["error"]:
575
- PrettyOutput.print(f"错误类型: {type(result['error']).__name__}", OutputType.WARNING)
576
-
577
- except KeyboardInterrupt:
578
- print("\n用户中断执行")
579
- break
580
- except Exception as e:
581
- PrettyOutput.print(f"执行出错: {str(e)}", OutputType.ERROR)
582
- continue
583
-
584
- return 0
585
-
586
- if __name__ == "__main__":
587
- exit(main())
588
-
589
- class FilePathCompleter(Completer):
590
- """文件路径自动完成器"""
591
-
592
- def __init__(self, root_dir: str):
593
- self.root_dir = root_dir
594
- self._file_list = None
595
-
596
- def _get_files(self) -> List[str]:
597
- """获取git管理的文件列表"""
598
- if self._file_list is None:
599
- try:
600
- # 切换到项目根目录
601
- old_cwd = os.getcwd()
602
- os.chdir(self.root_dir)
603
-
604
- # 获取git管理的文件列表
605
- self._file_list = os.popen("git ls-files").read().splitlines()
606
-
607
- # 恢复工作目录
608
- os.chdir(old_cwd)
609
- except Exception as e:
610
- PrettyOutput.print(f"获取文件列表失败: {str(e)}", OutputType.WARNING)
611
- self._file_list = []
612
- return self._file_list
613
-
614
- def get_completions(self, document, complete_event):
615
- """获取补全建议"""
616
- text_before_cursor = document.text_before_cursor
617
-
618
- # 检查是否刚输入了@
619
- if text_before_cursor.endswith('@'):
620
- # 显示所有文件
621
- for path in self._get_files():
622
- yield Completion(path, start_position=0)
623
- return
624
-
625
- # 检查之前是否有@,并获取@后的搜索词
626
- at_pos = text_before_cursor.rfind('@')
627
- if at_pos == -1:
628
- return
629
-
630
- search = text_before_cursor[at_pos + 1:].lower().strip()
631
-
632
- # 提供匹配的文件建议
633
- for path in self._get_files():
634
- path_lower = path.lower()
635
- if (search in path_lower or # 直接包含
636
- search in os.path.basename(path_lower) or # 文件名包含
637
- any(fnmatch.fnmatch(path_lower, f'*{s}*') for s in search.split())): # 通配符匹配
638
- # 计算正确的start_position
639
- yield Completion(path, start_position=-(len(search)))
640
-
641
- class SmartCompleter(Completer):
642
- """智能自动完成器,组合词语和文件路径补全"""
643
-
644
- def __init__(self, word_completer: WordCompleter, file_completer: FilePathCompleter):
645
- self.word_completer = word_completer
646
- self.file_completer = file_completer
647
-
648
- def get_completions(self, document, complete_event):
649
- """获取补全建议"""
650
- # 如果当前行以@结尾,使用文件补全
651
- if document.text_before_cursor.strip().endswith('@'):
652
- yield from self.file_completer.get_completions(document, complete_event)
653
- else:
654
- # 否则使用词语补全
655
- yield from self.word_completer.get_completions(document, complete_event)
656
-
657
- def get_multiline_input(prompt_text: str, root_dir: str = None) -> str:
658
- """获取多行输入,支持文件路径自动完成功能
659
-
660
- Args:
661
- prompt_text: 提示文本
662
- root_dir: 项目根目录,用于文件补全
663
-
664
- Returns:
665
- str: 用户输入的文本
666
- """
667
- # 创建文件补全器
668
- file_completer = FilePathCompleter(root_dir or os.getcwd())
669
-
670
- # 创建提示样式
671
- style = Style.from_dict({
672
- 'prompt': 'ansicyan bold',
673
- 'input': 'ansiwhite',
674
- })
675
-
676
- # 创建会话
677
- session = PromptSession(
678
- completer=file_completer,
679
- style=style,
680
- multiline=False,
681
- enable_history_search=True,
682
- complete_while_typing=True
683
- )
684
-
685
- # 显示初始提示文本
686
- print(f"\n{prompt_text}")
687
-
688
- # 创建提示符
689
- prompt = FormattedText([
690
- ('class:prompt', ">>> ")
691
- ])
692
-
693
- # 获取输入
694
- lines = []
695
- try:
696
- while True:
697
- line = session.prompt(prompt).strip()
698
- if not line: # 空行表示输入结束
699
- break
700
- lines.append(line)
701
- except KeyboardInterrupt:
702
- return "__interrupt__"
703
- except EOFError:
704
- pass
705
-
706
- return "\n".join(lines)