Skip to content
Merged

Beta #132

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions backend/ai_system/core_agents/main_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,29 @@ def _setup_tools(self):
},
}

# rename_section_title工具
rename_section_title_tool = {
"type": "function",
"function": {
"name": "rename_section_title",
"description": "修改paper.md文件中指定章节的标题,保持标题层级不变",
"parameters": {
"type": "object",
"properties": {
"old_title": {"type": "string", "description": "原章节标题(支持模糊匹配)"},
"new_title": {"type": "string", "description": "新章节标题"}
},
"required": ["old_title", "new_title"],
},
},
}

tools.extend([
analyze_template_tool,
get_section_content_tool,
update_section_content_tool,
add_section_tool
add_section_tool,
rename_section_title_tool
])

self.tools = tools
Expand Down Expand Up @@ -270,7 +288,8 @@ def _register_tool_functions(self):
"analyze_template": template_tool.analyze_template,
"get_section_content": template_tool.get_section_content,
"update_section_content": template_tool.update_section_content,
"add_section": template_tool.add_section
"add_section": template_tool.add_section,
"rename_section_title": template_tool.rename_section_title
})

async def _execute_code_agent_wrapper(self, task_prompt: str) -> str:
Expand Down
56 changes: 55 additions & 1 deletion backend/ai_system/core_tools/template_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,58 @@ async def add_section(self, section_title: str, content: str = "") -> str:

except Exception as e:
logger.error(f"添加章节失败: {e}")
return f"❌ 添加章节失败: {str(e)}"
return f"❌ 添加章节失败: {str(e)}"

async def rename_section_title(self, old_title: str, new_title: str) -> str:
"""修改paper.md文件中指定章节的标题"""
template_content = self._read_paper_md()
if not template_content:
return "错误:当前工作目录中没有找到paper.md文件"

try:
import re
lines = template_content.split('\n')
result_lines = []
title_found = False
original_title = ""
i = 0

while i < len(lines):
line = lines[i]
stripped_line = line.strip()

# 检查是否是目标标题
if (stripped_line.startswith('#') and
old_title.lower() in stripped_line.lower()):

# 使用正则表达式提取标题信息
header_match = re.match(r'^(#{1,6})\s+(.+)$', stripped_line)
if header_match:
level = header_match.group(1) # 保持原标题层级
original_title = header_match.group(2).strip()

# 创建新的标题行
new_line = f"{level} {new_title}"
result_lines.append(new_line)
title_found = True
i += 1
continue

result_lines.append(line)
i += 1

if not title_found:
return f"❌ 未找到匹配的标题: {old_title}"

# 保存修改后的内容
updated_content = '\n'.join(result_lines)
save_result = self._save_paper_md(updated_content)

if "✅" in save_result:
return f"✅ 标题修改成功:\"{original_title}\" → \"{new_title}\""
else:
return f"❌ 标题修改失败: {save_result}"

except Exception as e:
logger.error(f"修改标题失败: {e}")
return f"❌ 修改标题失败: {str(e)}"
2 changes: 2 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ dependencies = [
"pandas>=2.3.1",
"seaborn>=0.13.2",
"alembic>=1.16.5",
"python-docx>=1.1.2",
"markdown>=3.6.0",
]
110 changes: 110 additions & 0 deletions backend/services/file_services/workspace_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@
import shutil
import zipfile
import tempfile
import logging
from pathlib import Path
from fastapi import HTTPException, status, UploadFile
from typing import List, Dict, Any, Optional
from .file_helper import FileHelper
from ..data_services.utils import handle_service_errors

logger = logging.getLogger(__name__)

try:
from docx import Document
from docx.shared import Pt, Inches
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.style import WD_STYLE_TYPE
DOCX_AVAILABLE = True
except ImportError:
logger.warning("python-docx not available. DOCX export will be disabled.")
DOCX_AVAILABLE = False

class WorkspaceFileService:
def __init__(self):
self.base_path = Path("../pa_data/workspaces")
Expand Down Expand Up @@ -341,6 +354,18 @@ def export_workspace(self, work_id: str) -> str:
arcname = os.path.relpath(file_path, workspace_path)
zip_file.write(file_path, arcname)

# 生成并添加docx文件
try:
docx_content = self._generate_docx_from_paper(work_id)
if docx_content:
zip_file.writestr("paper.docx", docx_content)
logger.info(f"Successfully added paper.docx to workspace {work_id} export")
else:
logger.info(f"No paper.md found or docx generation failed for workspace {work_id}")
except Exception as e:
logger.error(f"Failed to generate docx for workspace {work_id}: {str(e)}")
# 继续导出其他文件,不因docx生成失败而中断整个导出过程

# 如果工作空间为空,添加一个空的README文件
if not os.listdir(workspace_path):
zip_file.writestr("README.txt", "This workspace is empty.")
Expand All @@ -355,5 +380,90 @@ def export_workspace(self, work_id: str) -> str:
detail=f"Failed to export workspace: {str(e)}"
)

def _generate_docx_from_paper(self, work_id: str) -> Optional[bytes]:
"""生成paper.md对应的docx文件内容"""
if not DOCX_AVAILABLE:
logger.warning("python-docx not available, skipping docx generation")
return None

try:
workspace_path = self.ensure_workspace_exists(work_id)
paper_md_path = workspace_path / "paper.md"

if not paper_md_path.exists():
logger.info(f"paper.md not found in workspace {work_id}, skipping docx generation")
return None

# 读取paper.md内容
with open(paper_md_path, 'r', encoding='utf-8') as f:
markdown_content = f.read()

# 转换为docx
docx_content = self._convert_markdown_to_docx(markdown_content)
return docx_content

except Exception as e:
logger.error(f"Failed to generate docx for workspace {work_id}: {str(e)}")
return None

def _convert_markdown_to_docx(self, markdown_content: str) -> bytes:
"""将Markdown内容转换为docx格式"""
try:
# 创建Word文档
doc = Document()

# 设置默认字体
style = doc.styles['Normal']
font = style.font
font.name = 'Times New Roman'
font.size = Pt(12)

# 分割Markdown内容为行
lines = markdown_content.split('\n')

i = 0
while i < len(lines):
line = lines[i].strip()

# 处理标题
if line.startswith('# '):
heading = doc.add_heading(line[2:], level=1)
heading.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
elif line.startswith('## '):
heading = doc.add_heading(line[3:], level=2)
elif line.startswith('### '):
heading = doc.add_heading(line[4:], level=3)
elif line.startswith('#### '):
heading = doc.add_heading(line[5:], level=4)
elif line.startswith('##### '):
heading = doc.add_heading(line[6:], level=5)
# 处理空行
elif line == '':
doc.add_paragraph()
# 处理普通段落
else:
# 简单的Markdown格式处理
processed_line = self._process_markdown_line(line)
paragraph = doc.add_paragraph(processed_line)

i += 1

# 保存到内存中的字节流
from io import BytesIO
doc_stream = BytesIO()
doc.save(doc_stream)
return doc_stream.getvalue()

except Exception as e:
logger.error(f"Failed to convert markdown to docx: {str(e)}")
raise

def _process_markdown_line(self, line: str) -> str:
"""处理单行Markdown格式,转换为纯文本"""
# 简单处理,去除一些常见的Markdown标记
line = line.replace('**', '').replace('*', '').replace('`', '')
line = line.replace('[', '').replace(']', '').replace('(', '').replace(')', '')
return line

# 创建全局实例
workspace_file_service = WorkspaceFileService()
5 changes: 5 additions & 0 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
host: '0.0.0.0', // 允许局域网访问
port: 5173, // 明确指定端口
strictPort: true, // 如果端口被占用则失败而不是尝试下一个端口
},
})