fix: 修复time未引包问题&状态工作台,文件列表递归调用接口的情况,递归放在后端
This commit is contained in:
parent
9c1e5e2154
commit
c5622fdef4
@ -141,6 +141,8 @@ services:
|
||||
- ./saves:/app/saves
|
||||
- ./docker/sandbox_provisioner/app.py:/app/app.py:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- "8002:8002"
|
||||
networks:
|
||||
- app-network
|
||||
extra_hosts:
|
||||
|
||||
@ -11,6 +11,7 @@ if sys.platform == "win32":
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
import time
|
||||
from collections import defaultdict, deque
|
||||
|
||||
import uvicorn
|
||||
|
||||
@ -516,6 +516,7 @@ async def resume_agent_chat(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""恢复被人工审批中断的对话(需要登录)"""
|
||||
|
||||
def normalize_resume_input(raw_answer: Any, raw_approved: bool | None) -> Any:
|
||||
if raw_answer is not None:
|
||||
if isinstance(raw_answer, str):
|
||||
@ -874,6 +875,7 @@ async def delete_thread_attachment(
|
||||
async def list_thread_files(
|
||||
thread_id: str,
|
||||
path: str = Query("/mnt/user-data"),
|
||||
recursive: bool = Query(False),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_required_user),
|
||||
):
|
||||
@ -883,6 +885,7 @@ async def list_thread_files(
|
||||
current_user_id=str(current_user.id),
|
||||
db=db,
|
||||
path=path,
|
||||
recursive=recursive,
|
||||
)
|
||||
|
||||
|
||||
@ -923,11 +926,7 @@ async def get_thread_artifact(
|
||||
)
|
||||
|
||||
media_type = guess_type(file_path.name)[0] or "application/octet-stream"
|
||||
headers = (
|
||||
{"Content-Disposition": f'attachment; filename="{file_path.name}"'}
|
||||
if download
|
||||
else None
|
||||
)
|
||||
headers = {"Content-Disposition": f'attachment; filename="{file_path.name}"'} if download else None
|
||||
return FileResponse(path=file_path, media_type=media_type, headers=headers)
|
||||
|
||||
|
||||
|
||||
@ -34,6 +34,7 @@ async def list_thread_files_view(
|
||||
current_user_id: str,
|
||||
db,
|
||||
path: str | None = None,
|
||||
recursive: bool = False,
|
||||
) -> dict:
|
||||
conv_repo = ConversationRepository(db)
|
||||
await require_user_conversation(conv_repo, thread_id, str(current_user_id))
|
||||
@ -50,6 +51,9 @@ async def list_thread_files_view(
|
||||
if not actual_path.is_dir():
|
||||
raise HTTPException(status_code=400, detail="path must be a directory")
|
||||
|
||||
if recursive:
|
||||
return _list_files_recursive(thread_id, actual_path, virtual_path)
|
||||
|
||||
entries: list[dict[str, Any]] = []
|
||||
for child in sorted(actual_path.iterdir(), key=lambda item: (not item.is_dir(), item.name.lower())):
|
||||
stat = child.stat()
|
||||
@ -70,6 +74,35 @@ async def list_thread_files_view(
|
||||
return {"path": virtual_path, "files": entries}
|
||||
|
||||
|
||||
def _list_files_recursive(thread_id: str, actual_path: Path, virtual_path: str) -> dict:
|
||||
entries: list[dict[str, Any]] = []
|
||||
|
||||
def _scan_dir(base_actual_path: Path, base_virtual_path: str):
|
||||
try:
|
||||
for child in sorted(base_actual_path.iterdir(), key=lambda item: (not item.is_dir(), item.name.lower())):
|
||||
stat = child.stat()
|
||||
child_virtual_path = virtual_path_for_thread_file(thread_id, child)
|
||||
entries.append(
|
||||
{
|
||||
"path": child_virtual_path,
|
||||
"name": child.name,
|
||||
"is_dir": child.is_dir(),
|
||||
"size": stat.st_size if child.is_file() else 0,
|
||||
"modified_at": _to_iso8601(stat.st_mtime),
|
||||
"artifact_url": None
|
||||
if child.is_dir()
|
||||
else f"/api/chat/thread/{thread_id}/artifacts/{child_virtual_path.lstrip('/')}",
|
||||
}
|
||||
)
|
||||
if child.is_dir():
|
||||
_scan_dir(child, child_virtual_path)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
_scan_dir(actual_path, virtual_path)
|
||||
return {"path": virtual_path, "files": entries}
|
||||
|
||||
|
||||
async def read_thread_file_content_view(
|
||||
*,
|
||||
thread_id: str,
|
||||
|
||||
9
uv.lock
9
uv.lock
@ -3402,6 +3402,9 @@ wheels = [
|
||||
binary = [
|
||||
{ name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
|
||||
]
|
||||
pool = [
|
||||
{ name = "psycopg-pool" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-binary"
|
||||
@ -5653,7 +5656,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "yuxi-know"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "agent-sandbox" },
|
||||
@ -5697,7 +5700,7 @@ dependencies = [
|
||||
{ name = "openai" },
|
||||
{ name = "opencv-python-headless" },
|
||||
{ name = "pillow" },
|
||||
{ name = "psycopg", extra = ["binary"] },
|
||||
{ name = "psycopg", extra = ["binary", "pool"] },
|
||||
{ name = "pyjwt" },
|
||||
{ name = "pymilvus" },
|
||||
{ name = "pymupdf" },
|
||||
@ -5782,7 +5785,7 @@ requires-dist = [
|
||||
{ name = "openai", specifier = ">=1.109" },
|
||||
{ name = "opencv-python-headless", specifier = ">=4.11.0.86" },
|
||||
{ name = "pillow", specifier = ">=10.5.0" },
|
||||
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.0" },
|
||||
{ name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.3.3" },
|
||||
{ name = "pyjwt", specifier = ">=2.8.0" },
|
||||
{ name = "pymilvus", specifier = ">=2.5.8" },
|
||||
{ name = "pymupdf", specifier = ">=1.25.5" },
|
||||
|
||||
@ -340,10 +340,11 @@ export const threadApi = {
|
||||
* 列出线程文件(目录)
|
||||
* @param {string} threadId
|
||||
* @param {string} path
|
||||
* @param {boolean} recursive
|
||||
* @returns {Promise}
|
||||
*/
|
||||
listThreadFiles: (threadId, path = '/mnt/user-data') =>
|
||||
apiGet(`/api/chat/thread/${threadId}/files?path=${encodeURIComponent(path)}`),
|
||||
listThreadFiles: (threadId, path = '/mnt/user-data', recursive = false) =>
|
||||
apiGet(`/api/chat/thread/${threadId}/files?path=${encodeURIComponent(path)}&recursive=${recursive}`),
|
||||
|
||||
/**
|
||||
* 读取线程文本文件内容(分页)
|
||||
|
||||
@ -758,26 +758,9 @@ const fetchThreadMessages = async ({ agentId, threadId, delay = 0 }) => {
|
||||
|
||||
const fetchThreadFiles = async (threadId) => {
|
||||
if (!threadId) return
|
||||
const queue = ['/mnt/user-data']
|
||||
const visited = new Set()
|
||||
const entries = []
|
||||
|
||||
try {
|
||||
while (queue.length > 0) {
|
||||
const currentPath = queue.shift()
|
||||
if (!currentPath || visited.has(currentPath)) continue
|
||||
visited.add(currentPath)
|
||||
|
||||
const response = await threadApi.listThreadFiles(threadId, currentPath)
|
||||
const files = Array.isArray(response?.files) ? response.files : []
|
||||
files.forEach((item) => {
|
||||
if (!item || typeof item.path !== 'string') return
|
||||
entries.push(item)
|
||||
if (item.is_dir === true) {
|
||||
queue.push(item.path)
|
||||
}
|
||||
})
|
||||
}
|
||||
const response = await threadApi.listThreadFiles(threadId, '/mnt/user-data', true)
|
||||
const entries = Array.isArray(response?.files) ? response.files : []
|
||||
threadFilesMap.value[threadId] = entries
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch thread files:', error)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user