Skip to content

Commit 99b8981

Browse files
committed
add
1 parent 239420c commit 99b8981

File tree

11 files changed

+215
-159
lines changed

11 files changed

+215
-159
lines changed

backend/app/core/agents.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -282,24 +282,17 @@ async def run(
282282
self,
283283
prompt: str,
284284
available_images: list[str] = None,
285-
static_prefix: str = "/static/",
286285
) -> str:
287286
"""
288287
执行写作任务
289288
Args:
290289
prompt: 写作提示
291290
available_images: 可用的图片相对路径列表(如 20250420-173744-9f87792c/编号_分布.png)
292-
static_prefix: 静态资源前缀
293291
"""
294292
if available_images:
295293
self.available_images = available_images
296294
# 拼接成完整URL
297-
image_list = "\n".join(
298-
[
299-
f"- {static_prefix}{img if img.startswith('/') else '/' + img}"
300-
for img in available_images
301-
]
302-
)
295+
image_list = ",".join(available_images)
303296
image_prompt = f"\n可用的图片链接列表:\n{image_list}\n请在写作时适当引用这些图片链接。"
304297
prompt = prompt + image_prompt
305298

backend/app/core/llm.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from app.schemas.response import AgentMessage, CoderMessage, WriterMessage
66
from app.utils.enums import AgentType
77
from app.utils.redis_manager import redis_manager
8+
import re
89

910

1011
class LLM:
@@ -79,6 +80,13 @@ async def send_message(self, agent_name, content, code=""):
7980
if agent_name == "CoderAgent":
8081
agent_msg: CoderMessage = CoderMessage(content=content, code=code)
8182
elif agent_name == "WriterAgent":
83+
# 判断content是否包含图片 xx.png,对其处理为 http://localhost:8000/static/20250428-200915-ebc154d4/512.jpg
84+
if re.search(r"\.(png|jpg|jpeg|gif|bmp|webp)$", content):
85+
content = re.sub(
86+
r"\.(png|jpg|jpeg|gif|bmp|webp)$",
87+
lambda match: f"http://localhost:8000/static/{self.task_id}/{match.group(0)}",
88+
content,
89+
)
8290
agent_msg: WriterMessage = WriterMessage(content=content)
8391
else:
8492
raise ValueError(f"无效的agent_name: {agent_name}")

backend/app/core/prompts.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ def get_writer_prompt(
7575
skill:熟练掌握{format_output}排版,
7676
output:你需要按照要求的格式排版,只输出{format_output}排版的内容
7777
78-
1. 当你输入图像引用时候,你需要将用户输入的文件名称路径切换为相对路径
79-
如用户输入文件路径image_name.png,你转化为../jupyter/image_name.png,就可正确引用显示
78+
1. 当你输入图像引用时候,image_name.png,就可正确引用显示
8079
2. 你不需要输出markdown的这个```markdown格式,只需要输出markdown的内容
8180
3. 严格按照参考用户输入的格式模板以及**正确的编号顺序**
8281
4. 不需要询问用户

backend/app/schemas/response.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class CoderMessage(AgentMessage):
7272
agent_type: AgentType = AgentType.CODER
7373
code: str | None = None
7474
code_results: list[OutputItem] | None = None
75+
files: list[str] | None = None
7576

7677

7778
class WriterMessage(AgentMessage):

backend/app/tools/code_interpreter.py

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import asyncio
1818
from app.config.setting import settings
1919
import json
20+
import base64
21+
22+
from app.utils.common_utils import get_current_files
2023

2124

2225
def delete_color_control_char(string):
@@ -284,12 +287,19 @@ async def execute_code(self, code: str) -> tuple[str, bool, str]:
284287
f"[{item.format} 图片已生成,内容为 base64,未展示]"
285288
)
286289

290+
combined_text = "\n".join(text_to_gpt)
291+
292+
# 在代码执行完成后,立即同步文件
293+
try:
294+
await self.download_all_files_from_sandbox()
295+
logger.info("文件同步完成")
296+
except Exception as e:
297+
logger.error(f"文件同步失败: {str(e)}")
298+
287299
# 保存到分段内容
288300
## TODO: Base64 等图像需要优化
289301
await self._push_to_websocket(content_to_display)
290302

291-
combined_text = "\n".join(text_to_gpt)
292-
293303
return (
294304
combined_text,
295305
error_occurred,
@@ -302,6 +312,7 @@ async def _push_to_websocket(self, content_to_display: list[OutputItem] | None):
302312
agent_msg = CoderMessage(
303313
agent_type=AgentType.CODER,
304314
code_results=content_to_display,
315+
files=get_current_files(self.work_dir, "all"),
305316
)
306317
logger.debug(f"发送消息: {agent_msg.model_dump_json()}")
307318
await redis_manager.publish_message(
@@ -346,36 +357,71 @@ def get_code_output(self, section: str) -> str:
346357

347358
async def cleanup(self):
348359
"""清理资源并关闭沙箱"""
349-
350-
if not await self.sbx.is_running():
351-
logger.warning("沙箱已经关闭了")
352-
353360
try:
354361
if self.sbx:
355-
## TODO: 生成一个文件,下载一份文件
356-
await self.download_all_files_from_sandbox()
357-
await self.sbx.kill()
358-
logger.info("成功关闭沙箱环境")
362+
if await self.sbx.is_running():
363+
try:
364+
await self.download_all_files_from_sandbox()
365+
except Exception as e:
366+
logger.error(f"下载文件失败: {str(e)}")
367+
finally:
368+
await self.sbx.kill()
369+
logger.info("成功关闭沙箱环境")
370+
else:
371+
logger.warning("沙箱已经关闭,跳过清理步骤")
359372
except Exception as e:
360373
logger.error(f"清理沙箱环境失败: {str(e)}")
361-
raise
374+
# 这里可以选择不抛出异常,因为这是清理步骤
362375

363376
async def download_all_files_from_sandbox(self) -> None:
364-
"""从沙箱中下载所有文件"""
377+
"""从沙箱中下载所有文件并与本地同步"""
365378
try:
366-
files = await self.sbx.files.list("/home/user")
367-
for file in files:
368-
content = await self.sbx.files.read(file.path)
369-
output_path = os.path.join(self.work_dir, file.name)
370-
# 修复:确保写入文件时 content 一定是 bytes 类型
371-
if isinstance(content, str):
372-
content = content.encode("utf-8")
373-
with open(output_path, "wb") as f:
374-
f.write(content)
375-
logger.info(f"成功下载文件: {file.name}")
379+
# 获取沙箱中的文件列表
380+
sandbox_files = await self.sbx.files.list("/home/user")
381+
sandbox_files_dict = {f.name: f for f in sandbox_files}
382+
383+
# 获取本地文件列表
384+
local_files = set()
385+
if os.path.exists(self.work_dir):
386+
local_files = set(os.listdir(self.work_dir))
387+
388+
# 下载新文件或更新已修改的文件
389+
for file in sandbox_files:
390+
try:
391+
local_path = os.path.join(self.work_dir, file.name)
392+
should_download = True
393+
394+
# 检查文件是否需要更新
395+
if file.name in local_files:
396+
# 这里可以添加文件修改时间或内容哈希的比较
397+
# 暂时简单处理,有同名文件就更新
398+
pass
399+
400+
if should_download:
401+
content = await self.sbx.files.read(file.path)
402+
403+
# 确保目标目录存在
404+
os.makedirs(self.work_dir, exist_ok=True)
405+
406+
# 写入文件
407+
with open(local_path, "wb") as f:
408+
if isinstance(content, str):
409+
if "," in content: # 处理data URL格式
410+
content = content.split(",")[1]
411+
content = base64.b64decode(content)
412+
else:
413+
content = content.encode("utf-8")
414+
f.write(content)
415+
logger.info(f"同步文件: {file.name}")
416+
417+
except Exception as e:
418+
logger.error(f"同步文件 {file.name} 失败: {str(e)}")
419+
continue
420+
421+
logger.info("文件同步完成")
422+
376423
except Exception as e:
377-
logger.error(f"下载文件失败: {str(e)}")
378-
# 这里不再 raise,防止 cleanup 阶段因沙箱已关闭而报错
424+
logger.error(f"文件同步失败: {str(e)}")
379425

380426
async def shutdown_sandbox(self):
381427
"""关闭沙箱环境"""

frontend/src/components/Files.vue

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import { computed } from 'vue'
23
import Tree from '@/components/Tree.vue'
34
import {
45
SidebarContent,
@@ -8,49 +9,26 @@ import {
89
SidebarMenu,
910
} from '@/components/ui/sidebar'
1011
import { File } from 'lucide-vue-next'
12+
import { useTaskStore } from '@/stores/task'
1113
12-
// This is sample data.
13-
const data = {
14-
changes: [
15-
{
16-
file: 'README.md',
17-
state: 'M',
18-
},
19-
{
20-
file: 'api/hello/route.ts',
21-
state: 'U',
22-
},
23-
{
24-
file: 'app/layout.tsx',
25-
state: 'M',
26-
},
27-
],
28-
tree: [
29-
[
30-
'app',
31-
[
32-
'api',
33-
['hello', ['route.ts']],
34-
'page.tsx',
35-
'layout.tsx',
36-
['blog', ['page.tsx']],
37-
],
38-
],
39-
[
40-
'components',
41-
['ui', 'button.tsx', 'card.tsx'],
42-
'header.tsx',
43-
'footer.tsx',
44-
],
45-
['lib', ['util.ts']],
46-
['public', 'favicon.ico', 'vercel.svg'],
47-
'.eslintrc.json',
48-
'.gitignore',
49-
'next.config.js',
50-
'tailwind.config.js',
51-
'package.json',
52-
'README.md',
53-
],
14+
const taskStore = useTaskStore()
15+
16+
// 从消息中提取最新的文件列表
17+
const files = taskStore.files
18+
19+
// 将文件列表转换为树形结构
20+
const fileTree = computed(() => {
21+
return files.map(file => file)
22+
})
23+
24+
const handleFileClick = (file: string) => {
25+
// 处理文件点击
26+
console.log('File clicked:', file)
27+
}
28+
29+
const handleFileDownload = (file: string) => {
30+
// 处理文件下载
31+
console.log('Download file:', file)
5432
}
5533
</script>
5634

@@ -61,7 +39,11 @@ const data = {
6139
<SidebarGroupLabel>Files</SidebarGroupLabel>
6240
<SidebarGroupContent>
6341
<SidebarMenu>
64-
<Tree v-for="(item, index) in data.tree" :key="index" :item="item" />
42+
<div v-if="fileTree.length === 0" class="px-4 py-2 text-sm text-gray-500">
43+
No files
44+
</div>
45+
<Tree v-else v-for="(item, index) in fileTree" :key="index" :item="item" @click="handleFileClick(item)"
46+
@download="handleFileDownload(item)" />
6547
</SidebarMenu>
6648
</SidebarGroupContent>
6749
</SidebarGroup>

0 commit comments

Comments
 (0)