feat: 增强编码能力 - 新增代码编辑器、代码搜索、Git集成

- 新增 plugins/code_editor.py: 精准代码编辑(搜索替换、批量编辑、diff对比)
- 新增 plugins/code_search.py: 代码搜索与理解(跨文件搜索、符号提取、依赖分析)
- 新增 plugins/git_tools.py: Git 版本控制(status/diff/log/commit/branch)
- 新增 core/ 模块: 配置管理、引擎管理、对话处理、显示输出、代码沙箱
- 修复 requirements.txt: 使用正确的 pip 包名(Pillow、opencv-python 等)
- 更新 unified_tool_manager.py: 兼容 Plugin 和 Liugin 两种类名
- 更新系统提示词: 增强编程工作流指导

本次更新由 mimo 自动化执行,与开发者无关。
This commit is contained in:
Xiaoli Dev committed 2026-04-25 10:55:27 +08:00
commit e22652fbbb
53 files changed
+20067

No files matched your search

+315
View File
@@ -0,0 +1,315 @@
"""
对话处理模块 - 核心对话循环和工具调用
"""
import json
import re
import time
import threading
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from colorama import Fore, Style
logger = logging.getLogger(__name__)
class ConversationManager:
"""对话管理器 - 处理用户输入、AI 响应、工具调用循环"""
def __init__(self, cli):
self.cli = cli
self.history = []
self.max_history = 999999
def process(self, user_input: str):
"""处理完整的对话循环"""
self.history.append({"role": "user", "content": user_input})
tool_prompts = self._get_tool_prompts()
loop_count = 0
current_input = user_input
max_loops = 20
while loop_count < max_loops:
response = self._generate_with_animation(current_input, tool_prompts)
processed = self._process_thinking(response)
processed = self._process_code_blocks(processed)
try:
text_content, json_data = self._parse_response(processed)
if json_data:
if text_content:
self._add_and_display(text_content)
if isinstance(json_data, dict):
result = self._handle_dict(json_data, text_content, processed)
if result == "break":
break
elif result == "continue":
loop_count += 1
continue
elif isinstance(json_data, list):
result = self._handle_list(json_data, text_content, processed)
if result == "break":
break
elif result == "continue":
loop_count += 1
continue
else:
display = text_content if text_content else processed
self._add_and_display(display)
break
except json.JSONDecodeError:
text_content, json_data = self._parse_response(processed)
if json_data:
# Re-process with parsed data
pass
else:
self._add_and_display(processed)
break
if loop_count >= max_loops:
self.cli.display.output(f"AI 已达到最大循环次数 ({max_loops}),已停止")
def _handle_dict(self, data, text_content, processed):
"""处理 dict 类型的 JSON 数据"""
if data.get('action') == 'use_tool':
self.cli._execute_single_tool(data)
return "break"
if data.get('action') == 'continue':
content = data.get('content', '')
if content:
self._add_and_display(content)
return "continue"
if data.get('continue') or data.get('need_continue') or data.get('think_more'):
msg = data.get('message', '')
if msg:
self.history.append({"role": "assistant", "content": msg})
self.cli.display.response(msg, is_continue=True)
return "continue"
msg = data.get('message', '')
if msg:
self._add_and_display(msg)
return "break"
if text_content:
self._add_and_display(text_content)
else:
self._add_and_display(processed)
return "break"
def _handle_list(self, data_list, text_content, processed):
"""处理 list 类型的 JSON 数据"""
tool_calls = [item for item in data_list
if isinstance(item, dict) and item.get('action') == 'use_tool']
if tool_calls:
self.cli._execute_concurrent_tools(tool_calls)
return "break"
continue_items = [item for item in data_list
if isinstance(item, dict) and
(item.get('continue') or item.get('need_continue') or item.get('think_more'))]
if continue_items:
msg = continue_items[0].get('message', '')
if msg:
self.history.append({"role": "assistant", "content": msg})
self.cli.display.response(msg, is_continue=True)
return "continue"
if text_content:
self._add_and_display(text_content)
else:
self._add_and_display(processed)
return "break"
def _add_and_display(self, content):
"""添加到历史并显示"""
self.history.append({"role": "assistant", "content": content})
self.cli.display.response(content)
def _generate_with_animation(self, user_input, tool_prompts=None):
"""生成 AI 响应(带动画)"""
system_prompt = self._build_system_prompt(tool_prompts)
if not self.cli.engine_manager.current_engine:
return "错误: 当前没有可用的AI引擎"
# TUI 模式:直接生成
if self.cli.display.tui_callback:
response = self.cli.engine_manager.generate_response(user_input, system_prompt)
if response:
self.history.append({"role": "assistant", "content": response})
return response
# CLI 模式:带动画
stop_event = threading.Event()
esc_event = threading.Event()
# 加载等待句子
sentences = self._load_love_sentences()
anim_thread = threading.Thread(
target=self.cli.display.thinking_animation,
args=(sentences, stop_event, esc_event), daemon=True)
anim_thread.start()
try:
response = self.cli.engine_manager.generate_response(user_input, system_prompt)
if response:
self.cli.display.typeprint(response, Fore.CYAN)
self.history.append({"role": "assistant", "content": response})
return response
finally:
stop_event.set()
anim_thread.join()
def _build_system_prompt(self, tool_prompts=None):
"""构建系统提示词"""
base = ("你叫小狸,是一个强大的智能编程助手。你具备精准的代码编辑能力、"
"完整的 Git 工作流支持、以及跨文件代码搜索和理解能力。"
"你可以帮助用户编写、修改、调试代码,管理版本控制,理解复杂代码库。")
tool_section = ""
if tool_prompts:
tool_section = f"\n\n{tool_prompts}"
call_format = """
【工具调用格式】
当需要执行操作时,在回复中包含 JSON 指令:
1. 纯 JSON: {"action": "use_tool", "tool": "工具名", "args": "参数"}
2. 文本+JSON: 好的,我来修改这个文件。{"action": "use_tool", "tool": "code_editor", "args": "edit file.py old_code <<<>>> new_code"}
【核心工具】
- code_editor: 精准代码编辑(edit/search/diff/insert/create/write)
- code_search: 代码搜索与理解(find/regex/symbols/imports/structure)
- git_tools: Git 操作(status/diff/log/add/commit/branch)
- cmd_executor: 执行 shell 命令
- file_manager: 文件管理
【编码工作流】
1. 先用 code_search 了解代码结构
2. 用 code_editor.read_range 查看相关代码
3. 用 code_editor.edit 精准修改
4. 用 git_tools 提交变更
【注意事项】
- code_editor.edit 的格式: old_text <<<>>> new_text(用 <<<>>> 分隔)
- args 必须是字符串
- 可以一次返回多个 JSON 指令顺序执行
- 使用 continue 补充回答: {"action": "continue", "content": "补充内容"}"""
return base + tool_section + call_format
def _process_thinking(self, response):
"""处理深度思考标记"""
# <think/> 格式
if '<think/>' in response:
parts = response.split('<think/>', 1)
if len(parts) >= 2:
thinking = parts[0].strip()
reply = parts[1].strip()
if thinking:
print(f"{Fore.LIGHTBLACK_EX}[思考: {thinking[:200]}...]{Style.RESET_ALL}" if len(thinking) > 200 else f"{Fore.LIGHTBLACK_EX}[思考: {thinking}]{Style.RESET_ALL}")
return reply if reply else ""
# <thinking>...</thinking> 格式
engine = self.cli.engine_manager.current_engine
if engine:
start = getattr(engine, 'thinking_start_marker', None)
end = getattr(engine, 'thinking_end_marker', None)
if start and end and start in response and end in response:
parts = response.split(start, 1)
if len(parts) > 1 and end in parts[1]:
thinking, reply = parts[1].split(end, 1)
if thinking.strip():
print(f"{Fore.LIGHTBLACK_EX}[思考: {thinking.strip()[:200]}]{Style.RESET_ALL}")
return reply.strip()
return response
def _process_code_blocks(self, text):
"""处理代码块标记"""
pattern = r'(""".*?""")'
def replace(m):
inner = m.group(1)[3:-3]
return f'{Fore.RED}{inner}{Style.RESET_ALL}'
return re.sub(pattern, replace, text, flags=re.DOTALL)
def _parse_response(self, response):
"""解析混合响应(文本+JSON)"""
response = response.strip()
# 直接 JSON
try:
parsed = json.loads(response)
if isinstance(parsed, (dict, list)):
return "", parsed
except json.JSONDecodeError:
pass
# 代码块中的 JSON
pattern = r"```(?:json)?\s*({.*?})\s*```"
matches = re.findall(pattern, response, re.DOTALL)
if matches:
try:
data = json.loads(matches[-1])
if isinstance(data, (dict, list)):
text = re.sub(pattern, "", response, flags=re.DOTALL).strip()
return text, data
except json.JSONDecodeError:
pass
# 内联 JSON
markers = ['{"action": "use_tool"', '{"action": "continue"',
'{"action":"use_tool"', '{"action":"continue"']
for marker in markers:
idx = response.find(marker)
if idx != -1:
brace_count = 0
for i in range(idx, len(response)):
if response[i] == '{':
brace_count += 1
elif response[i] == '}':
brace_count -= 1
if brace_count == 0:
try:
data = json.loads(response[idx:i+1])
if isinstance(data, dict) and data.get('action') in ['use_tool', 'continue']:
return response[:idx].strip(), data
except json.JSONDecodeError:
pass
break
return response, None
def _get_tool_prompts(self):
"""获取工具提示词"""
if not self.cli.tool_manager or not self.cli.tool_manager.tools:
return "当前没有可用的工具"
lines = ["可用工具列表:\n"]
for tool in self.cli.tool_manager.tools:
name = tool.get('name', '')
desc = tool.get('description', '')
lines.append(f"- {name}: {desc}")
return '\n'.join(lines)
def _load_love_sentences(self):
"""加载等待动画句子"""
import os
path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "love.txt")
if os.path.exists(path):
try:
with open(path, 'r', encoding='utf-8') as f:
return [l.strip() for l in f.readlines() if l.strip()]
except:
pass
return ["AI正在思考中...", "请稍等片刻...", "正在处理您的请求..."]