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