format: 格式化代码

This commit is contained in:
Wenjie Zhang 2026-03-24 22:37:03 +08:00
parent e07e88ae49
commit 4df242e63b
23 changed files with 76 additions and 93 deletions

View File

@ -27,9 +27,9 @@ from .sandbox import (
get_sandbox_base_port, get_sandbox_base_port,
get_sandbox_host, get_sandbox_host,
get_sandbox_image, get_sandbox_image,
get_sandbox_provider,
get_sandbox_provisioner_url, get_sandbox_provisioner_url,
get_sandbox_security_opts, get_sandbox_security_opts,
get_sandbox_provider,
) )
from .skills_backend import SelectedSkillsReadonlyBackend from .skills_backend import SelectedSkillsReadonlyBackend

View File

@ -4,8 +4,8 @@ from deepagents.backends import CompositeBackend, StateBackend
from yuxi.agents.middlewares.skills_middleware import normalize_selected_skills from yuxi.agents.middlewares.skills_middleware import normalize_selected_skills
from .skills_backend import SelectedSkillsReadonlyBackend
from .sandbox import get_sandbox_provider from .sandbox import get_sandbox_provider
from .skills_backend import SelectedSkillsReadonlyBackend
def _get_visible_skills_from_runtime(runtime) -> list[str]: def _get_visible_skills_from_runtime(runtime) -> list[str]:

View File

@ -16,10 +16,7 @@
- docker_api: Docker API 调用封装 - docker_api: Docker API 调用封装
""" """
from .sandbox_executor import YuxiSandboxBackend from .path_security import normalize_virtual_path
from .sandbox_info import SandboxInfo
from .sandbox_provisioner import YuxiSandboxProvider, get_sandbox_provider
from .sandbox_provisioner_base import SandboxBackend
from .sandbox_config import ( from .sandbox_config import (
IDLE_CHECK_INTERVAL, IDLE_CHECK_INTERVAL,
LARGE_TOOL_RESULTS_DIR, LARGE_TOOL_RESULTS_DIR,
@ -39,9 +36,12 @@ from .sandbox_config import (
get_sandbox_provisioner_url, get_sandbox_provisioner_url,
get_sandbox_security_opts, get_sandbox_security_opts,
) )
from .sandbox_executor import YuxiSandboxBackend
from .sandbox_info import SandboxInfo
from .sandbox_local_container import LocalContainerBackend from .sandbox_local_container import LocalContainerBackend
from .sandbox_provisioner import YuxiSandboxProvider, get_sandbox_provider
from .sandbox_provisioner_base import SandboxBackend
from .sandbox_remote import RemoteSandboxBackend from .sandbox_remote import RemoteSandboxBackend
from .path_security import normalize_virtual_path
__all__ = [ __all__ = [
# Executor # Executor

View File

@ -9,9 +9,9 @@ import urllib.parse
from yuxi.utils.logging_config import logger from yuxi.utils.logging_config import logger
from .docker_api import docker_api_request from .docker_api import docker_api_request
from .sandbox_provisioner_base import SandboxBackend
from .sandbox_config import get_sandbox_host, get_sandbox_security_opts from .sandbox_config import get_sandbox_host, get_sandbox_security_opts
from .sandbox_info import SandboxInfo from .sandbox_info import SandboxInfo
from .sandbox_provisioner_base import SandboxBackend
class LocalContainerBackend(SandboxBackend): class LocalContainerBackend(SandboxBackend):

View File

@ -14,10 +14,6 @@ from yuxi import config as sys_config
from yuxi.services.skill_service import get_skills_root_dir from yuxi.services.skill_service import get_skills_root_dir
from yuxi.utils.logging_config import logger from yuxi.utils.logging_config import logger
from .sandbox_local_container import LocalContainerBackend
from .sandbox_remote import RemoteSandboxBackend
from .sandbox_executor import YuxiSandboxBackend
from .sandbox_provisioner_base import SandboxBackend
from .sandbox_config import ( from .sandbox_config import (
IDLE_CHECK_INTERVAL, IDLE_CHECK_INTERVAL,
LARGE_TOOL_RESULTS_DIR, LARGE_TOOL_RESULTS_DIR,
@ -34,7 +30,11 @@ from .sandbox_config import (
get_sandbox_image, get_sandbox_image,
get_sandbox_provisioner_url, get_sandbox_provisioner_url,
) )
from .sandbox_executor import YuxiSandboxBackend
from .sandbox_info import SandboxInfo from .sandbox_info import SandboxInfo
from .sandbox_local_container import LocalContainerBackend
from .sandbox_provisioner_base import SandboxBackend
from .sandbox_remote import RemoteSandboxBackend
class YuxiSandboxProvider: class YuxiSandboxProvider:
@ -259,9 +259,7 @@ class YuxiSandboxProvider:
self._last_activity.pop(sandbox_id, None) self._last_activity.pop(sandbox_id, None)
self._warm_pool.pop(sandbox_id, None) self._warm_pool.pop(sandbox_id, None)
thread_ids = [ thread_ids = [
thread_id thread_id for thread_id, mapped_id in self._thread_sandboxes.items() if mapped_id == sandbox_id
for thread_id, mapped_id in self._thread_sandboxes.items()
if mapped_id == sandbox_id
] ]
for thread_id in thread_ids: for thread_id in thread_ids:
del self._thread_sandboxes[thread_id] del self._thread_sandboxes[thread_id]

View File

@ -13,17 +13,13 @@ class SandboxBackend(ABC):
thread_id: str, thread_id: str,
sandbox_id: str, sandbox_id: str,
extra_mounts: list[tuple[str, str, bool]] | None = None, extra_mounts: list[tuple[str, str, bool]] | None = None,
) -> SandboxInfo: ) -> SandboxInfo: ...
...
@abstractmethod @abstractmethod
def destroy(self, info: SandboxInfo) -> None: def destroy(self, info: SandboxInfo) -> None: ...
...
@abstractmethod @abstractmethod
def is_alive(self, info: SandboxInfo) -> bool: def is_alive(self, info: SandboxInfo) -> bool: ...
...
@abstractmethod @abstractmethod
def discover(self, sandbox_id: str) -> SandboxInfo | None: def discover(self, sandbox_id: str) -> SandboxInfo | None: ...
...

View File

@ -6,8 +6,8 @@ import requests
from yuxi.utils.logging_config import logger from yuxi.utils.logging_config import logger
from .sandbox_provisioner_base import SandboxBackend
from .sandbox_info import SandboxInfo from .sandbox_info import SandboxInfo
from .sandbox_provisioner_base import SandboxBackend
class RemoteSandboxBackend(SandboxBackend): class RemoteSandboxBackend(SandboxBackend):

View File

@ -21,6 +21,7 @@ from yuxi.services.subagent_service import get_subagents_from_names
from .prompt import PROMPT from .prompt import PROMPT
def _create_fs_backend(rt): def _create_fs_backend(rt):
"""创建文件存储后端(支持沙盒执行) """创建文件存储后端(支持沙盒执行)

View File

@ -39,11 +39,7 @@ class DeepAgent(BaseAgent):
description = "具备规划、深度分析和子智能体协作能力的智能体,可以处理复杂的多步骤任务" description = "具备规划、深度分析和子智能体协作能力的智能体,可以处理复杂的多步骤任务"
context_schema = DeepContext context_schema = DeepContext
capabilities = ["file_upload", "files", "todo"] # 支持文件上传功能 capabilities = ["file_upload", "files", "todo"] # 支持文件上传功能
metadata = { metadata = {"examples": ["调研一下多模态 GraphRAG 的相关论文"]}
"examples": [
"调研一下多模态 GraphRAG 的相关论文"
]
}
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)

View File

@ -92,7 +92,7 @@ class BaseContext:
default=sys_config.default_model, default=sys_config.default_model,
metadata={ metadata={
"name": "子智能体的默认模型", "name": "子智能体的默认模型",
"description": "子智能体的默认模型,会被子智能体的配置覆盖。", "description": "为所有子智能体设置默认模型,可在各子智能体配置中单独覆盖。",
}, },
) )

View File

@ -404,9 +404,8 @@ class SkillsMiddleware(AgentMiddleware):
return None return None
pure = PurePosixPath(raw if raw.startswith("/") else f"/{raw}") pure = PurePosixPath(raw if raw.startswith("/") else f"/{raw}")
parts = [p for p in pure.parts if p not in ("/", "")] parts = [p for p in pure.parts if p not in ("/", "")]
if len(parts) == 3 and parts[0] == "skills" and parts[2] == "SKILL.md": # 只支持 /mnt/skills/{slug}/SKILL.md 格式
slug = parts[1] if len(parts) == 4 and parts[0] == "mnt" and parts[1] == "skills" and parts[3] == "SKILL.md":
elif len(parts) == 4 and parts[0] == "mnt" and parts[1] == "skills" and parts[3] == "SKILL.md":
slug = parts[2] slug = parts[2]
else: else:
return None return None

View File

@ -4,15 +4,15 @@ import asyncio
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.agents.context import BaseContext
from yuxi.agents.backends import ( from yuxi.agents.backends import (
create_agent_composite_backend, create_agent_composite_backend,
resolve_sandbox_backend_async, resolve_sandbox_backend_async,
) )
from yuxi.agents.context import BaseContext
from yuxi.repositories.agent_config_repository import AgentConfigRepository from yuxi.repositories.agent_config_repository import AgentConfigRepository
from yuxi.repositories.conversation_repository import ConversationRepository from yuxi.repositories.conversation_repository import ConversationRepository
from yuxi.storage.postgres.models_business import User
from yuxi.services.conversation_service import require_user_conversation from yuxi.services.conversation_service import require_user_conversation
from yuxi.storage.postgres.models_business import User
async def _resolve_filesystem_context( async def _resolve_filesystem_context(

View File

@ -9,12 +9,11 @@ from urllib.parse import quote
from fastapi import HTTPException from fastapi import HTTPException
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.agents.backends.sandbox import SKILLS_PATH, USER_DATA_PATH from yuxi.agents.backends.sandbox import SKILLS_PATH, USER_DATA_PATH
from yuxi.agents.backends.skills_backend import SelectedSkillsReadonlyBackend from yuxi.agents.backends.skills_backend import SelectedSkillsReadonlyBackend
from yuxi.agents.middlewares.skills_middleware import normalize_selected_skills from yuxi.agents.middlewares.skills_middleware import normalize_selected_skills
from yuxi.storage.postgres.models_business import User
from yuxi.services.filesystem_service import _resolve_filesystem_state from yuxi.services.filesystem_service import _resolve_filesystem_state
from yuxi.storage.postgres.models_business import User
def _normalize_path(path: str | None) -> str: def _normalize_path(path: str | None) -> str:
@ -117,9 +116,7 @@ async def list_viewer_filesystem_tree(
) )
if normalized_path == "/": if normalized_path == "/":
entries = [ entries = [{"path": f"{USER_DATA_PATH}/", "name": "user-data", "is_dir": True, "size": 0, "modified_at": ""}]
{"path": f"{USER_DATA_PATH}/", "name": "user-data", "is_dir": True, "size": 0, "modified_at": ""}
]
if selected_skills: if selected_skills:
entries.append({"path": f"{SKILLS_PATH}/", "name": "skills", "is_dir": True, "size": 0, "modified_at": ""}) entries.append({"path": f"{SKILLS_PATH}/", "name": "skills", "is_dir": True, "size": 0, "modified_at": ""})
return {"entries": entries} return {"entries": entries}

View File

@ -67,7 +67,7 @@ $images = @(
"ghcr.io/astral-sh/uv:0.7.2", "ghcr.io/astral-sh/uv:0.7.2",
"nginx:alpine", "nginx:alpine",
"quay.io/coreos/etcd:v3.5.5", "quay.io/coreos/etcd:v3.5.5",
"postgres:16" "postgres:16",
"redis:7-alpine" "redis:7-alpine"
) )

View File

@ -476,7 +476,9 @@ const isStreaming = computed(() => {
}) })
const isProcessing = computed(() => isStreaming.value) const isProcessing = computed(() => isStreaming.value)
const isSendButtonDisabled = computed(() => { const isSendButtonDisabled = computed(() => {
return sendCooldownActive.value || (((!userInput.value || !currentAgent.value) && !isProcessing.value)) return (
sendCooldownActive.value || ((!userInput.value || !currentAgent.value) && !isProcessing.value)
)
}) })
const startSendCooldown = () => { const startSendCooldown = () => {

View File

@ -58,11 +58,7 @@
<!-- <a-divider /> --> <!-- <a-divider /> -->
<div class="config-segment" v-if="!isEmptyConfig"> <div class="config-segment" v-if="!isEmptyConfig">
<a-segmented <a-segmented v-model:value="currentSegment" :options="segmentOptions" block />
v-model:value="currentSegment"
:options="segmentOptions"
block
/>
</div> </div>
<div <div
@ -571,15 +567,15 @@ const hasOtherConfigs = computed(() => {
const segmentConfigKeys = computed(() => { const segmentConfigKeys = computed(() => {
const keys = Object.keys(configurableItems.value) const keys = Object.keys(configurableItems.value)
return { return {
model: keys.filter(key => { model: keys.filter((key) => {
const meta = configurableItems.value[key]?.template_metadata?.kind const meta = configurableItems.value[key]?.template_metadata?.kind
return meta === 'llm' || meta === 'prompt' return meta === 'llm' || meta === 'prompt'
}), }),
tools: keys.filter(key => { tools: keys.filter((key) => {
const meta = configurableItems.value[key]?.template_metadata?.kind const meta = configurableItems.value[key]?.template_metadata?.kind
return ['tools', 'knowledges', 'mcps', 'skills', 'subagents'].includes(meta) return ['tools', 'knowledges', 'mcps', 'skills', 'subagents'].includes(meta)
}), }),
other: keys.filter(key => { other: keys.filter((key) => {
const meta = configurableItems.value[key]?.template_metadata?.kind const meta = configurableItems.value[key]?.template_metadata?.kind
return !['llm', 'prompt', 'tools', 'knowledges', 'mcps', 'skills', 'subagents'].includes(meta) return !['llm', 'prompt', 'tools', 'knowledges', 'mcps', 'skills', 'subagents'].includes(meta)
}) })
@ -590,7 +586,7 @@ const filteredConfigurableItems = computed(() => {
if (isEmptyConfig.value) return {} if (isEmptyConfig.value) return {}
const keys = segmentConfigKeys.value[currentSegment.value] || [] const keys = segmentConfigKeys.value[currentSegment.value] || []
const filtered = {} const filtered = {}
keys.forEach(key => { keys.forEach((key) => {
filtered[key] = configurableItems.value[key] filtered[key] = configurableItems.value[key]
}) })
return filtered return filtered

View File

@ -136,7 +136,9 @@
<pre v-if="Array.isArray(currentFile?.content)">{{ <pre v-if="Array.isArray(currentFile?.content)">{{
formatContent(currentFile.content) formatContent(currentFile.content)
}}</pre> }}</pre>
<pre v-else-if="typeof currentFile?.content === 'string'" class="file-content-pre">{{ currentFile.content }}</pre> <pre v-else-if="typeof currentFile?.content === 'string'" class="file-content-pre">{{
currentFile.content
}}</pre>
<pre v-else>{{ JSON.stringify(currentFile, null, 2) }}</pre> <pre v-else>{{ JSON.stringify(currentFile, null, 2) }}</pre>
</template> </template>
</div> </div>
@ -392,7 +394,12 @@ const refreshFileSystem = async () => {
loadingFiles.value = true loadingFiles.value = true
filesystemError.value = '' filesystemError.value = ''
try { try {
const res = await getViewerFileSystemTree(props.threadId, '/', props.agentId, props.agentConfigId) const res = await getViewerFileSystemTree(
props.threadId,
'/',
props.agentId,
props.agentConfigId
)
if (res && res.entries) { if (res && res.entries) {
dynamicTreeData.value = sortEntries(res.entries).map((entry) => createTreeNode(entry)) dynamicTreeData.value = sortEntries(res.entries).map((entry) => createTreeNode(entry))
expandedKeys.value = [] expandedKeys.value = []
@ -413,9 +420,7 @@ onMounted(() => {
refreshFileSystem() refreshFileSystem()
}) })
watch( watch([() => props.threadId, () => props.agentId, () => props.agentConfigId], ([threadId]) => {
[() => props.threadId, () => props.agentId, () => props.agentConfigId],
([threadId]) => {
if (threadId) { if (threadId) {
refreshFileSystem() refreshFileSystem()
} else { } else {
@ -424,8 +429,7 @@ watch(
selectedKeys.value = [] selectedKeys.value = []
filesystemError.value = '' filesystemError.value = ''
} }
} })
)
const fileTreeData = computed(() => dynamicTreeData.value) const fileTreeData = computed(() => dynamicTreeData.value)

View File

@ -298,7 +298,6 @@
</a-spin> </a-spin>
</div> </div>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</template> </template>
</div> </div>

View File

@ -1,12 +1,9 @@
<template> <template>
<div class="model-providers-section"> <div class="model-providers-section">
<div class="header-section"> <div class="header-section">
<div class="header-content"> <div class="header-content">
<div class="section-title">自定义供应商</div> <div class="section-title">自定义供应商</div>
<p class="section-description"> <p class="section-description">添加自定义的LLM供应商支持OpenAI兼容的API格式</p>
添加自定义的LLM供应商支持OpenAI兼容的API格式
</p>
</div> </div>
<a-button type="primary" @click="openAddCustomProviderModal" class="add-btn lucide-icon-btn"> <a-button type="primary" @click="openAddCustomProviderModal" class="add-btn lucide-icon-btn">
<template #icon> <template #icon>
@ -103,7 +100,9 @@
<span class="stats-item unavailable"> {{ notModelKeys.length }} 未配置 </span> <span class="stats-item unavailable"> {{ notModelKeys.length }} 未配置 </span>
</div> </div>
</div> </div>
<p class="section-description">请在 <code>.env</code> 文件中配置对应的 APIKEY并重新启动服务</p> <p class="section-description">
请在 <code>.env</code> 文件中配置对应的 APIKEY并重新启动服务
</p>
<!-- 已配置的供应商 --> <!-- 已配置的供应商 -->
<div <div
@ -174,9 +173,7 @@
:width="800" :width="800"
> >
<div v-if="providerConfig.loading" class="modal-loading-container"> <div v-if="providerConfig.loading" class="modal-loading-container">
<a-spin <a-spin :indicator="h(LoaderCircle, { size: 32, color: 'var(--main-color)' })" />
:indicator="h(LoaderCircle, { size: 32, color: 'var(--main-color)' })"
/>
<div class="loading-text">正在获取模型列表...</div> <div class="loading-text">正在获取模型列表...</div>
</div> </div>
<div v-else class="modal-config-content"> <div v-else class="modal-config-content">
@ -822,7 +819,6 @@ const testCustomProvider = async (providerId, modelName) => {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.custom-providers-section { .custom-providers-section {
margin-bottom: 24px; margin-bottom: 24px;
.custom-provider-card { .custom-provider-card {
@ -918,7 +914,6 @@ const testCustomProvider = async (providerId, modelName) => {
} }
.builtin-providers-section { .builtin-providers-section {
.section-header { .section-header {
.providers-stats { .providers-stats {
display: flex; display: flex;

View File

@ -64,12 +64,7 @@
<a-button v-else-if="showButton" type="primary" @click="goToLogin"> 登录 </a-button> <a-button v-else-if="showButton" type="primary" @click="goToLogin"> 登录 </a-button>
<!-- 个人资料弹窗 --> <!-- 个人资料弹窗 -->
<a-modal <a-modal v-model:open="profileModalVisible" :footer="null" width="420px" class="profile-modal">
v-model:open="profileModalVisible"
:footer="null"
width="420px"
class="profile-modal"
>
<div class="profile-content"> <div class="profile-content">
<!-- 头像区域 --> <!-- 头像区域 -->
<div class="avatar-section"> <div class="avatar-section">
@ -92,7 +87,12 @@
@change="handleAvatarChange" @change="handleAvatarChange"
accept="image/*" accept="image/*"
> >
<a-button type="primary" class="lucide-icon-btn" size="small" :loading="avatarUploading"> <a-button
type="primary"
class="lucide-icon-btn"
size="small"
:loading="avatarUploading"
>
<template #icon><Upload size="14" /></template> <template #icon><Upload size="14" /></template>
{{ userStore.avatar ? '更换头像' : '上传头像' }} {{ userStore.avatar ? '更换头像' : '上传头像' }}
</a-button> </a-button>
@ -143,7 +143,7 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<div class="info-label">角色</div> <div class="info-label">角色</div>
<div class="info-value" :style="{'color': getRoleColor(userStore.userRole) }"> <div class="info-value" :style="{ color: getRoleColor(userStore.userRole) }">
{{ userRoleText }} {{ userRoleText }}
</div> </div>
</div> </div>

View File

@ -4,7 +4,9 @@
<div class="header-section"> <div class="header-section">
<div class="header-content"> <div class="header-content">
<div class="section-title">用户管理</div> <div class="section-title">用户管理</div>
<p class="section-description">管理系统用户请谨慎操作删除用户后该用户将无法登录系统</p> <p class="section-description">
管理系统用户请谨慎操作删除用户后该用户将无法登录系统
</p>
</div> </div>
<a-button type="primary" @click="showAddUserModal" class="add-btn lucide-icon-btn"> <a-button type="primary" @click="showAddUserModal" class="add-btn lucide-icon-btn">
<template #icon><Plus :size="16" /></template> <template #icon><Plus :size="16" /></template>

View File

@ -155,9 +155,7 @@ const initToolsChart = () => {
toolsChart = echarts.init(toolsChartRef.value) toolsChart = echarts.init(toolsChartRef.value)
const data = [...props.toolStats.most_used_tools] const data = [...props.toolStats.most_used_tools].sort((a, b) => a.count - b.count).slice(0, 10) // 10
.sort((a, b) => a.count - b.count)
.slice(0, 10) // 10
const option = { const option = {
tooltip: { tooltip: {

View File

@ -62,7 +62,9 @@
</p> </p>
<h1 class="title reveal-up delay-1">{{ infoStore.branding.title }}</h1> <h1 class="title reveal-up delay-1">{{ infoStore.branding.title }}</h1>
<Transition name="subtitle-switch" mode="out-in"> <Transition name="subtitle-switch" mode="out-in">
<p v-if="currentSubtitle" class="subtitle" :key="currentSubtitle">{{ currentSubtitle }}</p> <p v-if="currentSubtitle" class="subtitle" :key="currentSubtitle">
{{ currentSubtitle }}
</p>
</Transition> </Transition>
<!-- <p class="description">{{ infoStore.branding.description }}</p> --> <!-- <p class="description">{{ infoStore.branding.description }}</p> -->
<div class="hero-actions"> <div class="hero-actions">
@ -281,9 +283,7 @@ const getHeroBadgeText = (starsCount = null) => {
} }
const starValue = const starValue =
typeof starFeature === 'string' typeof starFeature === 'string' ? '' : (starFeature?.value || '').toString().trim()
? ''
: (starFeature?.value || '').toString().trim()
return starValue ? `已获得 ${starValue} GitHub Stars` : '已获得 GitHub Stars' return starValue ? `已获得 ${starValue} GitHub Stars` : '已获得 GitHub Stars'
} }