# workspace_browse.dspy - 浏览项目工作空间目录树 uid = await get_user() if not uid: uid = 'user-01' dbname = get_module_dbname('pipeline-sdlc') workspace_base = '/d/pipeline/workspaces' async with DBPools().sqlorContext(dbname) as sor: # 获取当前项目和工作空间目录 recs = await sor.sqlExe( "SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": uid}) pid = getattr(recs[0], 'current_project_id', '') if recs else '' ws_dir = '' if pid: proj = await sor.sqlExe( "SELECT name, workspace_dir FROM sd_projects WHERE id=${p}$", {"p": pid}) if proj: pname = getattr(proj[0], 'name', '') ws = getattr(proj[0], 'workspace_dir', '') or '' ws_dir = ws if ws.startswith('/') else workspace_base + '/' + pname # 安全限制 if not ws_dir or not ws_dir.startswith(workspace_base): return json.dumps({"error": "工作空间不可用"}, ensure_ascii=False) import os def build_tree(path, base): """递归构建目录树""" result = {"name": os.path.basename(path) or path, "path": path, "children": []} try: items = sorted(os.listdir(path)) except Exception: return result dirs = [] files = [] for item in items: full = os.path.join(path, item) if os.path.isdir(full) and not item.startswith('.') and item != '__pycache__': dirs.append(build_tree(full, base)) elif os.path.isfile(full) and not item.startswith('.'): size = os.path.getsize(full) ext = os.path.splitext(item)[1].lower() files.append({ "name": item, "path": full, "size": size, "ext": ext, "is_text": ext in ['.md','.py','.js','.html','.css','.json','.txt', '.xml','.yaml','.yml','.toml','.cfg','.sh','.sql', '.dspy','.ui','.java','.go','.rs','.ts','.tsx','.vue'], "is_media": ext in ['.png','.jpg','.jpeg','.gif','.svg','.mp4','.webm','.mp3','.wav'], }) result["children"] = dirs result["files"] = files return result tree = build_tree(ws_dir, ws_dir) return json.dumps({ "workspace": ws_dir, "project_id": pid, "tree": tree, }, ensure_ascii=False, default=str)