Merge branch 'develop' of codeup.aliyun.com:redbearai/python/redbear-mem-open into develop

This commit is contained in:
Mark
2026-01-05 11:49:08 +08:00
278 changed files with 21067 additions and 2675 deletions

View File

@@ -33,6 +33,7 @@ from . import (
emotion_config_controller,
prompt_optimizer_controller,
tool_controller,
home_page_controller,
)
from . import user_memory_controllers
@@ -70,5 +71,6 @@ manager_router.include_router(emotion_config_controller.router)
manager_router.include_router(prompt_optimizer_controller.router)
manager_router.include_router(memory_reflection_controller.router)
manager_router.include_router(tool_controller.router)
manager_router.include_router(home_page_controller.router)
__all__ = ["manager_router"]

View File

@@ -0,0 +1,29 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.core.response_utils import success
from app.db import get_db
from app.dependencies import get_current_user
from app.models.user_model import User
from app.schemas.response_schema import ApiResponse
from app.services.home_page_service import HomePageService
router = APIRouter(prefix="/home-page", tags=["Home Page"])
@router.get("/statistics", response_model=ApiResponse)
def get_home_statistics(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取首页统计数据"""
statistics = HomePageService.get_home_statistics(db, current_user.tenant_id)
return success(data=statistics, msg="统计数据获取成功")
@router.get("/workspaces", response_model=ApiResponse)
def get_workspace_list(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取工作空间列表"""
workspace_list = HomePageService.get_workspace_list(db, current_user.tenant_id)
return success(data=workspace_list, msg="工作空间列表获取成功")

View File

@@ -60,6 +60,22 @@ async def list_tools(
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{tool_id}/methods", response_model=ApiResponse)
async def get_tool_methods(
tool_id: str,
current_user: User = Depends(get_current_user),
service: ToolService = Depends(get_tool_service)
):
"""获取工具的所有方法"""
try:
methods = await service.get_tool_methods(tool_id, current_user.tenant_id)
if methods is None:
raise HTTPException(status_code=404, detail="工具不存在")
return success(data=methods, msg="获取工具方法成功")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{tool_id}", response_model=ApiResponse)
async def get_tool(
tool_id: str,
@@ -159,7 +175,8 @@ async def execute_tool(
workspace_id=current_user.current_workspace_id,
timeout=request.timeout
)
if not result.success:
raise HTTPException(status_code=400, detail=result["error"])
return success(
data={
"success": result.success,

View File

@@ -3,7 +3,7 @@ import secrets
from typing import Optional, Union
from datetime import datetime
from app.schemas.api_key_schema import ApiKeyType
from app.models.api_key_model import ApiKeyType
from fastapi import Response
from fastapi.responses import JSONResponse

View File

@@ -1,7 +1,7 @@
"""工具管理核心模块"""
from .base import BaseTool, ToolResult, ToolParameter
from .langchain_adapter import LangchainAdapter
from app.core.tools.base import BaseTool, ToolResult, ToolParameter
from app.core.tools.langchain_adapter import LangchainAdapter
# 可选导入,避免导入错误
try:

View File

@@ -193,7 +193,7 @@ class BaseTool(ABC):
def to_langchain_tool(self):
"""转换为Langchain工具格式"""
from .langchain_adapter import LangchainAdapter
from app.core.tools.langchain_adapter import LangchainAdapter
return LangchainAdapter.convert_tool(self)
def __repr__(self):

View File

@@ -1,11 +1,11 @@
"""内置工具模块"""
from .base import BuiltinTool
from .datetime_tool import DateTimeTool
from .json_tool import JsonTool
from .baidu_search_tool import BaiduSearchTool
from .mineru_tool import MinerUTool
from .textin_tool import TextInTool
from app.core.tools.builtin.base import BuiltinTool
from app.core.tools.builtin.datetime_tool import DateTimeTool
from app.core.tools.builtin.json_tool import JsonTool
from app.core.tools.builtin.baidu_search_tool import BaiduSearchTool
from app.core.tools.builtin.mineru_tool import MinerUTool
from app.core.tools.builtin.textin_tool import TextInTool
__all__ = [
"BuiltinTool",

View File

@@ -4,7 +4,7 @@ from typing import List, Dict, Any
import aiohttp
from app.core.tools.base import ToolParameter, ToolResult, ParameterType
from .base import BuiltinTool
from app.core.tools.builtin.base import BuiltinTool
class BaiduSearchTool(BuiltinTool):

View File

@@ -5,7 +5,7 @@ from typing import List
import pytz
from app.schemas.tool_schema import ToolParameter, ToolResult, ParameterType
from .base import BuiltinTool
from app.core.tools.builtin.base import BuiltinTool
class DateTimeTool(BuiltinTool):
@@ -27,7 +27,7 @@ class DateTimeTool(BuiltinTool):
type=ParameterType.STRING,
description="操作类型",
required=True,
enum=["format", "convert_timezone", "timestamp_to_datetime", "datetime_to_timestamp", "calculate", "now"]
enum=["format", "convert_timezone", "timestamp_to_datetime", "now"]
),
ToolParameter(
name="input_value",

View File

@@ -7,7 +7,7 @@ import xml.etree.ElementTree as ET
from xml.dom import minidom
from app.core.tools.base import ToolParameter, ToolResult, ParameterType
from .base import BuiltinTool
from app.core.tools.builtin.base import BuiltinTool
class JsonTool(BuiltinTool):
@@ -29,8 +29,7 @@ class JsonTool(BuiltinTool):
type=ParameterType.STRING,
description="操作类型",
required=True,
enum=["format", "minify", "validate", "convert", "to_yaml", "from_yaml", "to_xml", "from_xml", "merge",
"extract", "insert", "replace", "delete", "parse"]
enum=["insert", "replace", "delete", "parse"]
),
ToolParameter(
name="input_data",

View File

@@ -4,7 +4,7 @@ from typing import List, Dict, Any
import aiohttp
from app.core.tools.base import ToolParameter, ToolResult, ParameterType
from .base import BuiltinTool
from app.core.tools.builtin.base import BuiltinTool
class MinerUTool(BuiltinTool):

View File

@@ -4,7 +4,7 @@ from typing import List, Dict, Any
import aiohttp
from app.core.tools.base import ToolParameter, ToolResult, ParameterType
from .base import BuiltinTool
from app.core.tools.builtin.base import BuiltinTool
class TextInTool(BuiltinTool):

View File

@@ -1,8 +1,8 @@
"""自定义工具模块"""
from .base import CustomTool
from .schema_parser import OpenAPISchemaParser
from .auth_manager import AuthManager
from app.core.tools.custom.base import CustomTool
from app.core.tools.custom.schema_parser import OpenAPISchemaParser
from app.core.tools.custom.auth_manager import AuthManager
__all__ = [
"CustomTool",

View File

@@ -1,8 +1,8 @@
"""MCP工具模块"""
from .base import MCPTool
from .client import MCPClient, MCPConnectionPool
from .service_manager import MCPServiceManager
from app.core.tools.mcp.base import MCPTool
from app.core.tools.mcp.client import MCPClient, MCPConnectionPool
from app.core.tools.mcp.service_manager import MCPServiceManager
__all__ = [
"MCPTool",

View File

@@ -1,7 +1,6 @@
"""MCP工具基类"""
import time
from typing import Dict, Any, List
import aiohttp
from app.models.tool_model import ToolType
from app.core.tools.base import BaseTool

View File

@@ -204,7 +204,7 @@ class MCPClient:
)
init_response = json.loads(response)
if "error" in init_response:
if init_response.get("error", None) is not None:
raise MCPProtocolError(f"初始化失败: {init_response['error']}")
return True
@@ -325,7 +325,7 @@ class MCPClient:
try:
response = await self._send_request(request_data, timeout)
if "error" in response:
if response.get("error", None) is not None:
error = response["error"]
raise MCPProtocolError(f"工具调用失败: {error.get('message', '未知错误')}")

View File

@@ -8,7 +8,7 @@ from sqlalchemy.orm import Session
from app.models.tool_model import MCPToolConfig, ToolConfig, ToolType, ToolStatus
from app.core.logging_config import get_business_logger
from .client import MCPClient, MCPConnectionPool
from app.core.tools.mcp.client import MCPClient, MCPConnectionPool
logger = get_business_logger()

View File

@@ -219,17 +219,13 @@ class WorkflowExecutor:
# 创建节点实例(现在 start 和 end 也会被创建)
node_instance = NodeFactory.create_node(node, self.workflow_config)
if node_type in [NodeType.IF_ELSE, NodeType.HTTP_REQUEST]:
expressions = node_instance.build_conditional_edge_expressions()
# Number of branches, usually matches the number of conditional expressions
branch_number = len(expressions)
if node_type in [NodeType.IF_ELSE, NodeType.HTTP_REQUEST, NodeType.QUESTION_CLASSIFIER]:
# Find all edges whose source is the current node
related_edge = [edge for edge in self.edges if edge.get("source") == node_id]
# Iterate over each branch
for idx in range(branch_number):
for idx in range(len(related_edge)):
# Generate a condition expression for each edge
# Used later to determine which branch to take based on the node's output
# Assumes node output `node.<node_id>.output` matches the edge's label

View File

@@ -17,6 +17,8 @@ from app.core.workflow.nodes.node_factory import NodeFactory, WorkflowNode
from app.core.workflow.nodes.start import StartNode
from app.core.workflow.nodes.transform import TransformNode
from app.core.workflow.nodes.parameter_extractor import ParameterExtractorNode
from app.core.workflow.nodes.question_classifier import QuestionClassifierNode
from app.core.workflow.nodes.tool import ToolNode
__all__ = [
"BaseNode",
@@ -33,5 +35,7 @@ __all__ = [
"AssignerNode",
"HttpRequestNode",
"JinjaRenderNode",
"ParameterExtractorNode"
"ParameterExtractorNode",
"QuestionClassifierNode",
"ToolNode"
]

View File

@@ -21,6 +21,7 @@ from app.core.workflow.nodes.transform.config import TransformNodeConfig
from app.core.workflow.nodes.variable_aggregator.config import VariableAggregatorNodeConfig
from app.core.workflow.nodes.parameter_extractor.config import ParameterExtractorNodeConfig
from app.core.workflow.nodes.question_classifier.config import QuestionClassifierNodeConfig
from app.core.workflow.nodes.tool.config import ToolNodeConfig
from app.core.workflow.nodes.cycle_graph.config import LoopNodeConfig, IterationNodeConfig
__all__ = [
@@ -45,4 +46,5 @@ __all__ = [
"LoopNodeConfig",
"IterationNodeConfig",
"QuestionClassifierNodeConfig"
"ToolNodeConfig"
]

View File

@@ -24,6 +24,7 @@ from app.core.workflow.nodes.transform import TransformNode
from app.core.workflow.nodes.variable_aggregator import VariableAggregatorNode
from app.core.workflow.nodes.question_classifier import QuestionClassifierNode
from app.core.workflow.nodes.breaker import BreakNode
from app.core.workflow.nodes.tool import ToolNode
logger = logging.getLogger(__name__)
@@ -44,7 +45,8 @@ WorkflowNode = Union[
CycleGraphNode,
BreakNode,
ParameterExtractorNode,
QuestionClassifierNode
QuestionClassifierNode,
ToolNode
]
@@ -72,6 +74,7 @@ class NodeFactory:
NodeType.LOOP: CycleGraphNode,
NodeType.ITERATION: CycleGraphNode,
NodeType.BREAK: BreakNode,
NodeType.TOOL: ToolNode,
}
@classmethod

View File

@@ -26,4 +26,3 @@ class QuestionClassifierNodeConfig(BaseNodeConfig):
default="问题:{question}\n\n可选分类:{categories}\n\n补充指令:{supplement_prompt}\n\n请选择最合适的分类。",
description="用户提示词模板"
)
output_variable: str = Field(default="class_name", description="输出分类结果的变量名")

View File

@@ -12,6 +12,9 @@ from app.services.model_service import ModelConfigService
logger = logging.getLogger(__name__)
DEFAULT_CASE_PREFIX = "CASE"
DEFAULT_EMPTY_QUESTION_CASE = f"{DEFAULT_CASE_PREFIX}1"
class QuestionClassifierNode(BaseNode):
"""问题分类器节点"""
@@ -19,6 +22,7 @@ class QuestionClassifierNode(BaseNode):
def __init__(self, node_config: dict[str, Any], workflow_config: dict[str, Any]):
super().__init__(node_config, workflow_config)
self.typed_config = QuestionClassifierNodeConfig(**self.config)
self.category_to_case_map = self._build_category_case_map()
def _get_llm_instance(self) -> RedBearLLM:
"""获取LLM实例"""
@@ -47,48 +51,73 @@ class QuestionClassifierNode(BaseNode):
),
type=ModelType(model_type)
)
def _build_category_case_map(self) -> dict[str, str]:
"""
预构建 分类名称 -> CASE标识 的映射字典
示例:{"产品咨询": "CASE1", "售后问题": "CASE2"}
"""
category_map = {}
categories = self.typed_config.categories or []
for idx, class_item in enumerate(categories, start=1):
category_name = class_item.class_name.strip()
case_tag = f"{DEFAULT_CASE_PREFIX}{idx}"
category_map[category_name] = case_tag
return category_map
async def execute(self, state: WorkflowState) -> dict[str, Any]:
async def execute(self, state: WorkflowState) -> str:
"""执行问题分类"""
question = self.typed_config.input_variable
supplement_prompt = ""
if self.typed_config.user_supplement_prompt is not None:
supplement_prompt = self.typed_config.user_supplement_prompt
category_names = [class_item.class_name for class_item in self.typed_config.categories]
supplement_prompt = self.typed_config.user_supplement_prompt or ""
categories = self.typed_config.categories or []
category_names = [class_item.class_name.strip() for class_item in categories]
category_count = len(category_names)
if not question:
logger.warning(f"节点 {self.node_id} 未获取到输入问题")
return {self.typed_config.output_variable: category_names[0] if category_names else "unknown"}
llm = self._get_llm_instance()
# 渲染用户提示词模板,支持工作流变量
user_prompt = self._render_template(
self.typed_config.user_prompt.format(
question=question,
categories=", ".join(category_names),
supplement_prompt=supplement_prompt
),
state
)
messages = [
("system", self.typed_config.system_prompt),
("user", user_prompt),
]
response = await llm.ainvoke(messages)
result = response.content.strip()
if result in category_names:
category = result
else:
logger.warning(f"LLM返回了未知类别: {result}")
category = category_names[0] if category_names else "unknown"
logger.warning(
f"节点 {self.node_id} 未获取到输入问题,使用默认分支"
f"(默认分支:{DEFAULT_EMPTY_QUESTION_CASE},分类总数:{category_count}"
)
# 若分类列表为空返回默认unknown分支否则返回CASE1
return DEFAULT_EMPTY_QUESTION_CASE if category_count > 0 else "unknown"
log_supplement = supplement_prompt if supplement_prompt else ""
logger.info(f"节点 {self.node_id} 分类结果: {category}, 用户补充提示词:{log_supplement}")
return {self.typed_config.output_variable: category}
try:
llm = self._get_llm_instance()
# 渲染用户提示词模板,支持工作流变量
user_prompt = self._render_template(
self.typed_config.user_prompt.format(
question=question,
categories=", ".join(category_names),
supplement_prompt=supplement_prompt
),
state
)
messages = [
("system", self.typed_config.system_prompt),
("user", user_prompt),
]
response = await llm.ainvoke(messages)
result = response.content.strip()
if result in category_names:
category = result
else:
logger.warning(f"LLM返回了未知类别: {result}")
category = category_names[0] if category_names else "unknown"
log_supplement = supplement_prompt if supplement_prompt else ""
logger.info(f"节点 {self.node_id} 分类结果: {category}, 用户补充提示词:{log_supplement}")
return f"CASE{category_names.index(category) + 1}"
except Exception as e:
logger.error(
f"节点 {self.node_id} 分类执行异常:{str(e)}",
exc_info=True # 打印堆栈信息,便于调试
)
# 异常时返回默认分支,保证工作流容错性
if category_count > 0:
return DEFAULT_EMPTY_QUESTION_CASE
return "unknown"

View File

@@ -0,0 +1,4 @@
from app.core.workflow.nodes.tool.config import ToolNodeConfig
from app.core.workflow.nodes.tool.node import ToolNode
__all__ = ["ToolNode", "ToolNodeConfig"]

View File

@@ -0,0 +1,9 @@
from pydantic import Field
from app.core.workflow.nodes.base_config import BaseNodeConfig
class ToolNodeConfig(BaseNodeConfig):
"""工具节点配置"""
tool_id: str = Field(..., description="工具ID")
tool_parameters: dict[str, str] = Field(default_factory=dict, description="工具参数映射,支持工作流变量")

View File

@@ -0,0 +1,72 @@
import logging
import uuid
from typing import Any
from app.core.workflow.nodes.base_node import BaseNode, WorkflowState
from app.core.workflow.nodes.tool.config import ToolNodeConfig
from app.services.tool_service import ToolService
from app.db import get_db_read
logger = logging.getLogger(__name__)
class ToolNode(BaseNode):
"""工具节点"""
def __init__(self, node_config: dict[str, Any], workflow_config: dict[str, Any]):
super().__init__(node_config, workflow_config)
self.typed_config = ToolNodeConfig(**self.config)
async def execute(self, state: WorkflowState) -> dict[str, Any]:
"""执行工具"""
# 获取租户ID和用户ID
tenant_id = self.get_variable("sys.tenant_id", state)
user_id = self.get_variable("sys.user_id", state)
# 如果没有租户ID尝试从工作流ID获取
if not tenant_id:
workflow_id = self.get_variable("sys.workflow_id", state)
if workflow_id:
from app.repositories.tool_repository import ToolRepository
with get_db_read() as db:
tenant_id = ToolRepository.get_tenant_id_by_workflow_id(db, workflow_id)
if not tenant_id:
tenant_id = uuid.UUID("6c2c91b0-3f49-4489-9157-2208aa56a097")
# logger.error(f"节点 {self.node_id} 缺少租户ID")
# return {"error": "缺少租户ID"}
# 渲染工具参数
rendered_parameters = {}
for param_name, param_template in self.typed_config.tool_parameters.items():
rendered_value = self._render_template(param_template, state)
rendered_parameters[param_name] = rendered_value
logger.info(f"节点 {self.node_id} 执行工具 {self.typed_config.tool_id},参数: {rendered_parameters}")
print(self.typed_config.tool_id)
# 执行工具
with get_db_read() as db:
tool_service = ToolService(db)
result = await tool_service.execute_tool(
tool_id=self.typed_config.tool_id,
parameters=rendered_parameters,
tenant_id=tenant_id,
user_id=user_id
)
print(result)
if result.success:
logger.info(f"节点 {self.node_id} 工具执行成功")
return {
"success": True,
"data": result.data,
"execution_time": result.execution_time
}
else:
logger.error(f"节点 {self.node_id} 工具执行失败: {result.error}")
return {
"success": False,
"error": result.error,
"error_code": result.error_code,
"execution_time": result.execution_time
}

View File

@@ -0,0 +1,137 @@
from datetime import datetime, timedelta
from sqlalchemy.orm import Session
from sqlalchemy import func
from uuid import UUID
from typing import Dict
from app.models.end_user_model import EndUser
from app.models.user_model import User
from app.models.workspace_model import Workspace, WorkspaceMember
from app.models.models_model import ModelConfig
from app.models.app_model import App
class HomePageRepository:
@staticmethod
def get_model_statistics(db: Session, tenant_id: UUID, month_start: datetime) -> tuple[int, int]:
"""获取模型统计数据"""
total_models = db.query(ModelConfig).filter(
ModelConfig.tenant_id == tenant_id,
ModelConfig.is_active == True
).count()
new_models_this_month = db.query(ModelConfig).filter(
ModelConfig.tenant_id == tenant_id,
ModelConfig.is_active == True,
ModelConfig.created_at >= month_start
).count()
return total_models, new_models_this_month
@staticmethod
def get_workspace_statistics(db: Session, tenant_id: UUID, month_start: datetime) -> tuple[int, int]:
"""获取工作空间统计数据"""
active_workspaces = db.query(Workspace).filter(
Workspace.tenant_id == tenant_id,
Workspace.is_active == True
).count()
new_workspaces_this_month = db.query(Workspace).filter(
Workspace.tenant_id == tenant_id,
Workspace.is_active == True,
Workspace.created_at >= month_start
).count()
return active_workspaces, new_workspaces_this_month
@staticmethod
def get_user_statistics(db: Session, tenant_id: UUID, month_start: datetime) -> tuple[int, int]:
"""获取用户统计数据"""
workspace_ids = db.query(Workspace.id).filter(
Workspace.tenant_id == tenant_id,
Workspace.is_active == True
).subquery()
total_users = db.query(EndUser).join(
App,
EndUser.app_id == App.id
).filter(
App.workspace_id.in_(workspace_ids),
App.is_active == True,
App.status == "active"
).count()
new_users_this_month = db.query(EndUser).join(
App,
EndUser.app_id == App.id
).filter(
App.workspace_id.in_(workspace_ids),
App.is_active == True,
App.status == "active",
EndUser.created_at >= month_start
).count()
return total_users, new_users_this_month
@staticmethod
def get_app_statistics(db: Session, tenant_id: UUID, week_start: datetime) -> tuple[int, int]:
"""获取应用统计数据"""
workspace_ids = db.query(Workspace.id).filter(
Workspace.tenant_id == tenant_id,
Workspace.is_active == True
).subquery()
running_apps = db.query(App).filter(
App.workspace_id.in_(workspace_ids),
App.is_active == True,
App.status == "active"
).count()
new_apps_this_week = db.query(App).filter(
App.workspace_id.in_(workspace_ids),
App.is_active == True,
App.status == "active",
App.created_at >= week_start
).count()
return running_apps, new_apps_this_week
@staticmethod
def get_workspaces_with_counts(db: Session, tenant_id: UUID) -> tuple[list[Workspace], Dict[UUID, int], Dict[UUID, int]]:
"""批量获取工作空间及其统计数据"""
# 获取工作空间列表
workspaces = db.query(Workspace).filter(
Workspace.tenant_id == tenant_id,
Workspace.is_active == True
).all()
workspace_ids = [ws.id for ws in workspaces]
# 批量获取应用数量
app_counts = db.query(
App.workspace_id,
func.count(App.id).label('count')
).filter(
App.workspace_id.in_(workspace_ids),
App.is_active,
App.status == "active"
).group_by(App.workspace_id).all()
app_count_dict = {workspace_id: count for workspace_id, count in app_counts}
# 批量获取用户数量
user_counts = db.query(
App.workspace_id,
func.count(EndUser.id).label('count')
).join(
EndUser,
EndUser.app_id == App.id
).filter(
App.workspace_id.in_(workspace_ids),
App.is_active,
App.status == "active"
).group_by(App.workspace_id).all()
user_count_dict = {workspace_id: count for workspace_id, count in user_counts}
return workspaces, app_count_dict, user_count_dict

View File

@@ -1,10 +1,9 @@
"""工具数据访问层"""
import uuid
from typing import List, Optional, Dict, Any
from typing import List, Optional
from sqlalchemy.orm import Session
from sqlalchemy import func, or_
from sqlalchemy import func
from app.repositories.base_repository import BaseRepository
from app.models.tool_model import (
ToolConfig, BuiltinToolConfig, CustomToolConfig, MCPToolConfig,
ToolExecution, ToolType, ToolStatus
@@ -14,6 +13,31 @@ from app.models.tool_model import (
class ToolRepository:
"""工具仓储类"""
@staticmethod
def get_tenant_id_by_workflow_id(db: Session, workflow_id: uuid.UUID) -> Optional[uuid.UUID]:
"""根据工作流ID获取tenant_id
Args:
db: 数据库会话
workflow_id: 工作流配置ID
Returns:
tenant_id或None
"""
from app.models.app_model import App
from app.models.workflow_model import WorkflowConfig
from app.models.workspace_model import Workspace
result = db.query(Workspace.tenant_id).join(
App, App.workspace_id == Workspace.id
).join(
WorkflowConfig, WorkflowConfig.app_id == App.id
).filter(
WorkflowConfig.id == workflow_id
).first()
return result[0] if result else None
@staticmethod
def find_by_tenant(
db: Session,

View File

@@ -0,0 +1,32 @@
from datetime import datetime
from pydantic import BaseModel, field_serializer
from typing import Optional
from app.core.api_key_utils import datetime_to_timestamp
class HomeStatistics(BaseModel):
"""首页统计数据"""
total_models: int
new_models_this_month: int
active_workspaces: int
new_workspaces_this_month: int
total_users: int
new_users_this_month: int
running_apps: int
new_apps_this_week: int
class WorkspaceInfo(BaseModel):
"""工作空间信息"""
id: str
name: str
icon: Optional[str]
description: Optional[str]
app_count: int
user_count: int
created_at: datetime
@field_serializer('created_at')
@classmethod
def serialize_datetime(cls, v: datetime) -> Optional[int]:
return datetime_to_timestamp(v)

View File

@@ -0,0 +1,67 @@
from datetime import datetime, timedelta
from sqlalchemy.orm import Session
from uuid import UUID
from app.repositories.home_page_repository import HomePageRepository
from app.schemas.home_page_schema import HomeStatistics, WorkspaceInfo
class HomePageService:
@staticmethod
def get_home_statistics(db: Session, tenant_id: UUID) -> HomeStatistics:
"""获取首页统计数据"""
# 计算时间范围
now = datetime.now()
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
week_start = now - timedelta(days=now.weekday())
week_start = week_start.replace(hour=0, minute=0, second=0, microsecond=0)
# 获取各项统计数据
total_models, new_models_this_month = HomePageRepository.get_model_statistics(
db, tenant_id, month_start
)
active_workspaces, new_workspaces_this_month = HomePageRepository.get_workspace_statistics(
db, tenant_id, month_start
)
total_users, new_users_this_month = HomePageRepository.get_user_statistics(
db, tenant_id, month_start
)
running_apps, new_apps_this_week = HomePageRepository.get_app_statistics(
db, tenant_id, week_start
)
return HomeStatistics(
total_models=total_models,
new_models_this_month=new_models_this_month,
active_workspaces=active_workspaces,
new_workspaces_this_month=new_workspaces_this_month,
total_users=total_users,
new_users_this_month=new_users_this_month,
running_apps=running_apps,
new_apps_this_week=new_apps_this_week
)
@staticmethod
def get_workspace_list(db: Session, tenant_id: UUID) -> list[WorkspaceInfo]:
"""获取工作空间列表(优化版本)"""
workspaces, app_count_dict, user_count_dict= HomePageRepository.get_workspaces_with_counts(
db, tenant_id
)
workspace_list = []
for workspace in workspaces:
workspace_info = WorkspaceInfo(
id=str(workspace.id),
name=workspace.name,
icon=workspace.icon,
description=workspace.description,
app_count=app_count_dict.get(workspace.id, 0),
user_count=user_count_dict.get(workspace.id, 0),
created_at=workspace.created_at
)
workspace_list.append(workspace_info)
return workspace_list

View File

@@ -297,6 +297,165 @@ class ToolService:
self.db.commit()
logger.info(f"租户 {tenant_id} 内置工具初始化完成")
async def get_tool_methods(self, tool_id: str, tenant_id: uuid.UUID) -> Optional[List[Dict[str, Any]]]:
"""获取工具的所有方法
Args:
tool_id: 工具ID
tenant_id: 租户ID
Returns:
方法列表或None
"""
config = self._get_tool_config(tool_id, tenant_id)
if not config:
return None
try:
if config.tool_type == ToolType.BUILTIN.value:
return await self._get_builtin_tool_methods(config)
elif config.tool_type == ToolType.CUSTOM.value:
return await self._get_custom_tool_methods(config)
elif config.tool_type == ToolType.MCP.value:
return await self._get_mcp_tool_methods(config)
else:
return []
except Exception as e:
logger.error(f"获取工具方法失败: {tool_id}, {e}")
return []
async def _get_builtin_tool_methods(self, config: ToolConfig) -> List[Dict[str, Any]]:
"""获取内置工具的方法"""
builtin_config = self.builtin_repo.find_by_tool_id(self.db, config.id)
if not builtin_config or builtin_config.tool_class not in BUILTIN_TOOLS:
return []
# 获取工具实例
tool_instance = self._get_tool_instance(str(config.id), config.tenant_id)
if not tool_instance:
return []
# 检查是否有operation参数
operation_param = None
for param in tool_instance.parameters:
if param.name == "operation" and param.enum:
operation_param = param
break
if operation_param:
# 有多个操作
methods = []
for operation in operation_param.enum:
methods.append({
"method_id": f"{config.name}_{operation}",
"name": operation,
"description": f"{config.description} - {operation}",
"parameters": [p for p in tool_instance.parameters if p.name != "operation"]
})
return methods
else:
# 只有一个方法
return [{
"method_id": config.name,
"name": config.name,
"description": config.description,
"parameters": [p for p in tool_instance.parameters if p.name != "operation"]
}]
async def _get_custom_tool_methods(self, config: ToolConfig) -> List[Dict[str, Any]]:
"""获取自定义工具的方法"""
custom_config = self.custom_repo.find_by_tool_id(self.db, config.id)
if not custom_config:
return []
try:
from app.core.tools.custom.schema_parser import OpenAPISchemaParser
parser = OpenAPISchemaParser()
# 解析schema
if custom_config.schema_content:
success, schema, error = parser.parse_from_content(custom_config.schema_content, "application/json")
elif custom_config.schema_url:
success, schema, error = await parser.parse_from_url(custom_config.schema_url)
else:
return []
if not success:
return []
# 提取操作
tool_info = parser.extract_tool_info(schema)
operations = tool_info.get("operations", {})
methods = []
for operation_id, operation in operations.items():
# 生成参数列表
parameters = []
# 路径和查询参数
for param_name, param_info in operation.get("parameters", {}).items():
parameters.append({
"name": param_name,
"type": param_info.get("type", "string"),
"description": param_info.get("description", ""),
"required": param_info.get("required", False),
"enum": param_info.get("enum"),
"default": param_info.get("default")
})
# 请求体参数
request_body = operation.get("request_body")
if request_body:
schema_props = request_body.get("schema", {}).get("properties", {})
required_props = request_body.get("schema", {}).get("required", [])
for prop_name, prop_schema in schema_props.items():
parameters.append({
"name": prop_name,
"type": prop_schema.get("type", "string"),
"description": prop_schema.get("description", ""),
"required": prop_name in required_props,
"enum": prop_schema.get("enum"),
"default": prop_schema.get("default")
})
methods.append({
"method_id": operation_id,
"name": operation.get("summary", operation_id),
"description": operation.get("description", ""),
"method": operation.get("method", "GET"),
"path": operation.get("path", "/"),
"parameters": parameters
})
return methods
except Exception as e:
logger.error(f"解析自定义工具schema失败: {e}")
return []
async def _get_mcp_tool_methods(self, config: ToolConfig) -> List[Dict[str, Any]]:
"""获取MCP工具的方法"""
mcp_config = self.mcp_repo.find_by_tool_id(self.db, config.id)
if not mcp_config:
return []
available_tools = mcp_config.available_tools or []
if not available_tools:
return []
methods = []
for tool_name in available_tools:
methods.append({
"method_id": tool_name,
"name": tool_name,
"description": f"MCP工具: {tool_name}",
"parameters": [] # MCP工具参数需要动态获取
})
return methods
def get_tool_statistics(self, tenant_id: uuid.UUID) -> Dict[str, Any]:
"""获取工具统计信息"""
try:

38
api_key_mcp_server.py Normal file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""API Key认证MCP服务器"""
from fastapi import FastAPI, HTTPException, Depends, Header
from typing import Optional
import uvicorn
from mcp_base import MCPRequest, handle_mcp_request, TOOLS
app = FastAPI(title="API Key MCP Server", version="1.0.0")
# API Key配置
API_KEYS = {"test-api-key", "demo-key-123"}
def verify_api_key(x_api_key: Optional[str] = Header(None)):
"""验证API Key"""
if x_api_key and x_api_key in API_KEYS:
return True
raise HTTPException(status_code=401, detail="Invalid API Key")
@app.get("/")
async def root():
return {"name": "API Key MCP Server", "version": "1.0.0", "auth_type": "api_key"}
@app.get("/health")
async def health():
return {"status": "healthy", "tools": len(TOOLS), "auth_type": "api_key"}
@app.post("/mcp")
async def mcp_handler(request: MCPRequest, _: bool = Depends(verify_api_key)):
return await handle_mcp_request(request, "API Key MCP Server")
if __name__ == "__main__":
print("启动API Key认证MCP服务器...")
print("访问 http://localhost:8004 查看服务状态")
print("MCP端点: http://localhost:8004/mcp")
print("认证方式: API Key (Header: X-API-Key)")
print("测试API Keys: test-api-key, demo-key-123")
uvicorn.run(app, host="0.0.0.0", port=8004)

45
basic_auth_mcp_server.py Normal file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Basic Auth认证MCP服务器"""
from fastapi import FastAPI, HTTPException, Depends, Header
from typing import Optional
import uvicorn
import base64
from mcp_base import MCPRequest, handle_mcp_request, TOOLS
app = FastAPI(title="Basic Auth MCP Server", version="1.0.0")
# Basic Auth配置
BASIC_AUTH_USERS = {"admin": "password", "user": "secret"}
def verify_basic_auth(authorization: Optional[str] = Header(None)):
"""验证Basic Auth"""
if authorization and authorization.startswith("Basic "):
try:
credentials = base64.b64decode(authorization.split(" ")[1]).decode()
username, password = credentials.split(":", 1)
if username in BASIC_AUTH_USERS and BASIC_AUTH_USERS[username] == password:
return True
except:
pass
raise HTTPException(status_code=401, detail="Invalid Basic Auth")
@app.get("/")
async def root():
return {"name": "Basic Auth MCP Server", "version": "1.0.0", "auth_type": "basic_auth"}
@app.get("/health")
async def health():
return {"status": "healthy", "tools": len(TOOLS), "auth_type": "basic_auth"}
@app.post("/mcp")
async def mcp_handler(request: MCPRequest, _: bool = Depends(verify_basic_auth)):
return await handle_mcp_request(request, "Basic Auth MCP Server")
if __name__ == "__main__":
print("启动Basic Auth认证MCP服务器...")
print("访问 http://localhost:8006 查看服务状态")
print("MCP端点: http://localhost:8006/mcp")
print("认证方式: Basic Auth (Header: Authorization: Basic <base64>)")
print("测试用户: admin:password, user:secret")
uvicorn.run(app, host="0.0.0.0", port=8006)

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Bearer Token认证MCP服务器"""
from fastapi import FastAPI, HTTPException, Depends, Header
from typing import Optional
import uvicorn
from mcp_base import MCPRequest, handle_mcp_request, TOOLS
app = FastAPI(title="Bearer Token MCP Server", version="1.0.0")
# Bearer Token配置
BEARER_TOKENS = {"bearer-token-123", "demo-bearer-token"}
def verify_bearer_token(authorization: Optional[str] = Header(None)):
"""验证Bearer Token"""
if authorization and authorization.startswith("Bearer "):
token = authorization.split(" ")[1]
if token in BEARER_TOKENS:
return True
raise HTTPException(status_code=401, detail="Invalid Bearer Token")
@app.get("/")
async def root():
return {"name": "Bearer Token MCP Server", "version": "1.0.0", "auth_type": "bearer_token"}
@app.get("/health")
async def health():
return {"status": "healthy", "tools": len(TOOLS), "auth_type": "bearer_token"}
@app.post("/mcp")
async def mcp_handler(request: MCPRequest, _: bool = Depends(verify_bearer_token)):
return await handle_mcp_request(request, "Bearer Token MCP Server")
if __name__ == "__main__":
print("启动Bearer Token认证MCP服务器...")
print("访问 http://localhost:8005 查看服务状态")
print("MCP端点: http://localhost:8005/mcp")
print("认证方式: Bearer Token (Header: Authorization: Bearer <token>)")
print("测试Bearer Tokens: bearer-token-123, demo-bearer-token")
uvicorn.run(app, host="0.0.0.0", port=8005)

111
mcp_base.py Normal file
View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""MCP服务器基础模块 - 共享的模型和处理逻辑"""
from pydantic import BaseModel
from typing import Dict, Any
class MCPRequest(BaseModel):
jsonrpc: str = "2.0"
id: str
method: str
params: Dict[str, Any] = {}
class MCPResponse(BaseModel):
jsonrpc: str = "2.0"
id: str
result: Any = None
error: Dict[str, Any] = None
# 工具定义
TOOLS = [
{
"name": "calculator",
"description": "简单计算器",
"inputSchema": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式"}
},
"required": ["expression"]
}
},
{
"name": "echo",
"description": "回显工具",
"inputSchema": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "要回显的消息"}
},
"required": ["message"]
}
}
]
async def handle_mcp_request(request: MCPRequest, server_name: str = "MCP Server"):
"""处理MCP请求"""
try:
if request.method == "initialize":
return MCPResponse(
id=request.id,
result={
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {"listChanged": True}},
"serverInfo": {"name": server_name, "version": "1.0.0"}
}
)
elif request.method == "tools/list":
return MCPResponse(
id=request.id,
result={"tools": TOOLS}
)
elif request.method == "tools/call":
tool_name = request.params.get("name")
arguments = request.params.get("arguments", {})
if tool_name == "calculator":
try:
expression = arguments.get("expression", "")
result = eval(expression)
return MCPResponse(
id=request.id,
result={"content": [{"type": "text", "text": f"结果: {result}"}]}
)
except Exception as e:
return MCPResponse(
id=request.id,
error={"code": -1, "message": f"计算错误: {str(e)}"}
)
elif tool_name == "echo":
message = arguments.get("message", "")
return MCPResponse(
id=request.id,
result={"content": [{"type": "text", "text": f"Echo: {message}"}]}
)
else:
return MCPResponse(
id=request.id,
error={"code": -1, "message": f"未知工具: {tool_name}"}
)
elif request.method == "ping":
return MCPResponse(
id=request.id,
result={"status": "pong"}
)
else:
return MCPResponse(
id=request.id,
error={"code": -1, "message": f"未知方法: {request.method}"}
)
except Exception as e:
return MCPResponse(
id=request.id,
error={"code": -1, "message": str(e)}
)

130
simple_mcp_server.py Normal file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env python3
"""简化的MCP服务器 - 用于测试MCP工具集成"""
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict, Any, List
import uvicorn
app = FastAPI(title="Simple MCP Server", version="1.0.0")
class MCPRequest(BaseModel):
jsonrpc: str = "2.0"
id: str
method: str
params: Dict[str, Any] = {}
class MCPResponse(BaseModel):
jsonrpc: str = "2.0"
id: str
result: Any = None
error: Dict[str, Any] = None
# 可用工具定义
TOOLS = [
{
"name": "calculator",
"description": "简单计算器",
"inputSchema": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式"}
},
"required": ["expression"]
}
},
{
"name": "echo",
"description": "回显工具",
"inputSchema": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "要回显的消息"}
},
"required": ["message"]
}
}
]
@app.get("/")
async def root():
return {"name": "Simple MCP Server", "version": "1.0.0"}
@app.get("/health")
async def health():
return {"status": "healthy", "tools": len(TOOLS)}
@app.post("/mcp")
async def mcp_handler(request: MCPRequest):
"""处理MCP请求"""
try:
if request.method == "initialize":
return MCPResponse(
id=request.id,
result={
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {"listChanged": True}},
"serverInfo": {"name": "Simple MCP Server", "version": "1.0.0"}
}
)
elif request.method == "tools/list":
return MCPResponse(
id=request.id,
result={"tools": TOOLS}
)
elif request.method == "tools/call":
tool_name = request.params.get("name")
arguments = request.params.get("arguments", {})
if tool_name == "calculator":
try:
expression = arguments.get("expression", "")
result = eval(expression) # 注意生产环境不要用eval
return MCPResponse(
id=request.id,
result={"content": [{"type": "text", "text": f"结果: {result}"}]}
)
except Exception as e:
return MCPResponse(
id=request.id,
error={"code": -1, "message": f"计算错误: {str(e)}"}
)
elif tool_name == "echo":
message = arguments.get("message", "")
return MCPResponse(
id=request.id,
result={"content": [{"type": "text", "text": f"Echo: {message}"}]}
)
else:
return MCPResponse(
id=request.id,
error={"code": -1, "message": f"未知工具: {tool_name}"}
)
elif request.method == "ping":
return MCPResponse(
id=request.id,
result={"status": "pong"}
)
else:
return MCPResponse(
id=request.id,
error={"code": -1, "message": f"未知方法: {request.method}"}
)
except Exception as e:
return MCPResponse(
id=request.id,
error={"code": -1, "message": str(e)}
)
if __name__ == "__main__":
print("启动简化MCP服务器...")
print("访问 http://localhost:8002 查看服务状态")
print("MCP端点: http://localhost:8002/mcp")
uvicorn.run(app, host="0.0.0.0", port=8002)

2
web/.gitignore vendored
View File

@@ -22,5 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
vite.config.js
package-lock.json

View File

@@ -1,7 +1,7 @@
{
"name": "memory-bear-font-end",
"private": true,
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"scripts": {
"dev": "vite",
@@ -10,10 +10,18 @@
"preview": "vite preview"
},
"dependencies": {
"@antv/layout": "^1.2.14-beta.8",
"@antv/x6": "^3.0.1",
"@antv/x6-react-shape": "^3.0.1",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@lexical/code": "^0.39.0",
"@lexical/link": "^0.39.0",
"@lexical/list": "^0.39.0",
"@lexical/react": "^0.39.0",
"@lexical/rich-text": "^0.39.0",
"antd": "^5.27.4",
"axios": "^1.12.2",
"clsx": "^2.1.1",
@@ -23,6 +31,8 @@
"echarts": "^5.6.0",
"echarts-for-react": "^3.0.2",
"i18next": "^25.6.0",
"js-yaml": "^4.1.1",
"lexical": "^0.39.0",
"mermaid": "^11.12.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -31,7 +41,6 @@
"react-markdown": "^10.1.0",
"react-router-dom": "^6.22.0",
"react-syntax-highlighter": "^16.1.0",
"reactflow": "^11.11.4",
"rehype-katex": "^7.0.1",
"rehype-raw": "^7.0.0",
"remark-breaks": "^4.0.0",
@@ -46,6 +55,7 @@
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.14",
"@types/crypto-js": "^4.2.2",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24.6.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",

View File

@@ -6,22 +6,31 @@
// biome-ignore lint: disable
export {}
declare global {
const Activity: typeof import('react').Activity
const Fragment: typeof import('react').Fragment
const Link: typeof import('react-router-dom').Link
const NavLink: typeof import('react-router-dom').NavLink
const Navigate: typeof import('react-router-dom').Navigate
const Outlet: typeof import('react-router-dom').Outlet
const Route: typeof import('react-router-dom').Route
const Routes: typeof import('react-router-dom').Routes
const Suspense: typeof import('react').Suspense
const cache: typeof import('react').cache
const cacheSignal: typeof import('react').cacheSignal
const createContext: typeof import('react').createContext
const createRef: typeof import('react').createRef
const forwardRef: typeof import('react').forwardRef
const lazy: typeof import('react').lazy
const memo: typeof import('react').memo
const startTransition: typeof import('react').startTransition
const use: typeof import('react').use
const useActionState: typeof import('react').useActionState
const useCallback: typeof import('react').useCallback
const useContext: typeof import('react').useContext
const useDebugValue: typeof import('react').useDebugValue
const useDeferredValue: typeof import('react').useDeferredValue
const useEffect: typeof import('react').useEffect
const useEffectEvent: typeof import('react').useEffectEvent
const useHref: typeof import('react-router-dom').useHref
const useId: typeof import('react').useId
const useImperativeHandle: typeof import('react').useImperativeHandle
@@ -33,6 +42,7 @@ declare global {
const useMemo: typeof import('react').useMemo
const useNavigate: typeof import('react-router-dom').useNavigate
const useNavigationType: typeof import('react-router-dom').useNavigationType
const useOptimistic: typeof import('react').useOptimistic
const useOutlet: typeof import('react-router-dom').useOutlet
const useOutletContext: typeof import('react-router-dom').useOutletContext
const useParams: typeof import('react-router-dom').useParams

View File

@@ -27,12 +27,21 @@ import 'dayjs/locale/en'
import 'dayjs/locale/zh-cn'
import 'dayjs/plugin/timezone'
import 'dayjs/plugin/utc'
import { cookieUtils } from './utils/request';
function App() {
const { t } = useTranslation();
const { locale, language, timeZone } = useI18n()
useEffect(() => {
const authToken = cookieUtils.get('authToken')
if (!authToken && !window.location.hash.includes('#/login')) {
window.location.href = `/#/login`;
}
}, [])
useEffect(() => {
document.title = t('memoryBear')

33
web/src/api/apiKey.ts Normal file
View File

@@ -0,0 +1,33 @@
import { request } from '@/utils/request'
import type { ApiKey } from '@/views/ApiKeyManagement/types'
// API Key列表
export const getApiKeyListUrl = '/apikeys'
export const getApiKeyList = (data: Record<string, unknown>) => {
return request.get(getApiKeyListUrl, data)
}
// API Key详情
export const getApiKey = (id: string) => {
return request.get(`/apikeys/${id}`)
}
// 创建API Key
export const createApiKey = (values: ApiKey) => {
return request.post('/apikeys', values)
}
// 更新API Key
export const updateApiKey = (id: string, values: ApiKey) => {
return request.put(`/apikeys/${id}`, values)
}
// 删除 API Key
export const deleteApiKey = (id: string) => {
return request.delete(`/apikeys/${id}`)
}
// 使用统计
export const getApiKeyStats = (app_key_id: string) => {
return request.get(`/apikeys/${app_key_id}/stats`)
}

View File

@@ -1,7 +1,9 @@
import { request } from '@/utils/request'
import type { Application } from '@/views/ApplicationManagement/types'
import type { ApplicationModalData } from '@/views/ApplicationManagement/types'
import type { Config } from '@/views/ApplicationConfig/types'
import { handleSSE } from '@/utils/stream'
import { handleSSE, type SSEMessage } from '@/utils/stream'
import type { QueryParams } from '@/views/Conversation/types'
import type { WorkflowConfig } from '@/views/Workflow/types'
// 应用列表
export const getApplicationListUrl = '/apps'
@@ -12,20 +14,24 @@ export const getApplicationList = (data: Record<string, unknown>) => {
export const getApplicationConfig = (id: string) => {
return request.get(`/apps/${id}/config`)
}
// 获取集群应配置
// 获取集群应配置
export const getMultiAgentConfig = (id: string) => {
return request.get(`/apps/${id}/multi-agent`)
}
// 获取 workflow应用配置
export const getWorkflowConfig = (id: string) => {
return request.get(`/apps/${id}/workflow`)
}
// 应用详情
export const getApplication = (id: string) => {
return request.get(`/apps/${id}`)
}
// 更新应用
export const updateApplication = (id: string, values: Application) => {
export const updateApplication = (id: string, values: ApplicationModalData) => {
return request.put(`/apps/${id}`, values)
}
// 创建应用
export const addApplication = (values: Application) => {
export const addApplication = (values: ApplicationModalData) => {
return request.post('/apps', values)
}
// 保存Agent配置
@@ -36,11 +42,15 @@ export const saveAgentConfig = (app_id: string, values: Config) => {
export const saveMultiAgentConfig = (app_id: string, values: Config) => {
return request.put(`/apps/${app_id}/multi-agent`, values)
}
// 保存workflow配置
export const saveWorkflowConfig = (app_id: string, values: WorkflowConfig) => {
return request.put(`/apps/${app_id}/workflow`, values)
}
// 模型比对试运行
export const runCompare = (app_id: string, values: Record<string, unknown>, onMessage?: (data: string) => void) => {
export const runCompare = (app_id: string, values: Record<string, unknown>, onMessage?: (data: SSEMessage[]) => void) => {
return handleSSE(`/apps/${app_id}/draft/run/compare`, values, onMessage)
}
export const draftRun = (app_id: string, values: Record<string, unknown>, onMessage?: (data: string) => void) => {
export const draftRun = (app_id: string, values: Record<string, unknown>, onMessage?: (data: SSEMessage[]) => void) => {
return handleSSE(`/apps/${app_id}/draft/run`, values, onMessage)
}
// 删除应用
@@ -76,18 +86,7 @@ export const getConversationHistory = (share_token: string, data: { page: number
})
}
// 发送体验对话
export const sendConversation = (share_token: string, values: {
message: string;
web_search: boolean;
memory: boolean;
stream: boolean;
conversation_id: string | null;
}, onMessage, shareToken: string) => {
// return request.post(`/public/share/chat`, values, {
// headers: {
// 'Authorization': `Bearer ${localStorage.getItem(`shareToken_${share_token}`)}`
// }
// })
export const sendConversation = (values: QueryParams, onMessage: (data: SSEMessage[]) => void, shareToken: string) => {
return handleSSE(`/public/share/chat`, values, onMessage, {
headers: {
'Authorization': `Bearer ${shareToken}`

View File

@@ -64,8 +64,8 @@ export const getModelTypeList = async () => {
return response as any[];
};
// 获取模型列表
export const getModelList = async (type: string | string[], pageInfo: PageRequest) => {
const response = await request.get(`${apiPrefix}/models`, { type, ...pageInfo });
export const getModelList = async (pageInfo: PageRequest) => {
const response = await request.get(`${apiPrefix}/models`, pageInfo);
return response as any;
};
//获取模型提供者
@@ -135,16 +135,18 @@ interface UploadFileOptions {
kb_id?: string;
parent_id?: string;
onUploadProgress?: (event: AxiosProgressEvent) => void;
signal?: AbortSignal;
}
// 上传文件
export const uploadFile = async (data: FormData, options?: UploadFileOptions) => {
const { kb_id, parent_id, onUploadProgress } = options || {};
const { kb_id, parent_id, onUploadProgress, signal } = options || {};
const params: Record<string, string> = {};
if (kb_id) params.kb_id = kb_id;
if (parent_id) params.parent_id = parent_id;
const response = await request.uploadFile(`${apiPrefix}/files/file`, data, {
params,
onUploadProgress,
signal,
});
return response as UploadFileResponse;
};
@@ -199,8 +201,8 @@ export const deleteFile = async (id: string) => {
};
// 获取文档列表
export const getDocumentList = async (query: PathQuery) => {
const response = await request.get(`${apiPrefix}/documents/${query.kb_id}/${query.parent_id}/documents`, query);
export const getDocumentList = async (kb_id:string, query: PathQuery) => {
const response = await request.get(`${apiPrefix}/documents/${kb_id}/documents`, query);
return response as KnowledgeBaseDocumentData[];
};
// 文档详情
@@ -213,6 +215,11 @@ export const createDocument = async (data: KnowledgeBaseDocumentData) => {
const response = await request.post(`${apiPrefix}/documents/document`, data);
return response as KnowledgeBaseDocumentData;
};
// 自定义文档上传并创建
export const createDocumentAndUpload = async ( data: any, params: PathQuery) => {
const response = await request.post(`${apiPrefix}/files/customtext`, data, { params } );
return response as any;
};
// 更新文档
export const updateDocument = async (id: string, data: KnowledgeBaseDocumentData) => {
const response = await request.put(`${apiPrefix}/documents/${id}`, data);
@@ -223,9 +230,9 @@ export const deleteDocument = async (id: string) => {
const response = await request.delete(`${apiPrefix}/documents/${id}`);
return response;
};
// 文档解析
export const parseDocument = async (id: string) => {
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`);
// 文档解析 / 分块
export const parseDocument = async (id: string, data: any) => {
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`, data);
return response as any;
};
// 文档分块预览

View File

@@ -8,7 +8,15 @@ import type {
import type {
ConfigForm as ExtractionConfigForm
} from '@/views/MemoryExtractionEngine/types'
import type {
ConfigForm as EmotionConfig
} from '@/views/EmotionEngine/types'
import type {
ConfigForm as SelfReflectionEngineConfig
} from '@/views/SelfReflectionEngine/types'
import type { TestParams } from '@/views/MemoryConversation'
import type { EndUser } from '@/views/UserMemoryDetail/types'
import { handleSSE, type SSEMessage } from '@/utils/stream'
// 记忆对话
export const readService = (query: TestParams) => {
@@ -59,6 +67,7 @@ export const getTotalEndUsers = () => {
export const getUserProfile = (end_user_id: string) => {
return request.get(`/memory/analytics/user_profile`, { end_user_id })
}
// 用户记忆-记忆洞察
export const getMemoryInsightReport = (end_user_id: string) => {
return request.get(`/memory-storage/analytics/memory_insight/report`, { end_user_id })
@@ -67,9 +76,20 @@ export const getMemoryInsightReport = (end_user_id: string) => {
export const getUserSummary = (end_user_id: string) => {
return request.get(`/memory-storage/analytics/user_summary`, { end_user_id })
}
// 记忆分类
export const getNodeStatistics = (end_user_id: string) => {
return request.get(`/memory-storage/analytics/node_statistics`, { end_user_id })
}
// 基本信息
export const getEndUserProfile = (end_user_id: string) => {
return request.get(`/memory-storage/read_end_user/profile`, { end_user_id })
}
export const updatedEndUserProfile = (values: EndUser) => {
return request.post(`/memory-storage/updated_end_user/profile`, values)
}
// 用户记忆-关系网络
export const getMemorySearchEdges = (end_user_id: string) => {
return request.get(`/memory-storage/search/entity_graph`, { end_user_id })
return request.get(`/memory-storage/analytics/graph_data`, { end_user_id })
}
// 用户记忆-用户兴趣分布
export const getHotMemoryTagsByUser = (end_user_id: string) => {
@@ -95,6 +115,26 @@ export const getChunkInsight = (end_user_id: string) => {
export const getRagContent = (end_user_id: string) => {
return request.get(`/dashboard/rag_content`, { end_user_id, limit: 20 })
}
// 情感分布分析
export const getWordCloud = (group_id: string) => {
return request.post(`/memory/emotion/wordcloud`, { group_id, limit: 20 })
}
// 高频情绪关键词
export const getEmotionTags = (group_id: string) => {
return request.post(`/memory/emotion/tags`, { group_id, limit: 20 })
}
// 情绪健康指数
export const getEmotionHealth = (group_id: string) => {
return request.post(`/memory/emotion/health`, { group_id, limit: 20 })
}
// 个性化建议
export const getEmotionSuggestions = (group_id: string) => {
return request.post(`/memory/emotion/suggestions`, { group_id, limit: 20 })
}
export const analyticsRefresh = (end_user_id: string) => {
return request.post('/memory-storage/analytics/generate_cache', { end_user_id })
}
/*************** end 用户记忆 相关接口 ******************************/
/****************** 记忆管理 相关接口 *******************************/
@@ -117,11 +157,11 @@ export const deleteMemoryConfig = (config_id: number) => {
}
// 遗忘引擎-获取配置
export const getMemoryForgetConfig = (config_id: number | string) => {
return request.get('/memory-storage/read_config_forget', { config_id })
return request.get('/memory/forget/read_config', { config_id })
}
// 遗忘引擎-更新配置
export const updateMemoryForgetConfig = (values: ForgetConfigForm) => {
return request.post('/memory-storage/update_config_forget', values)
return request.post('/memory/forget/update_config', values)
}
// 记忆萃取引擎-获取配置
export const getMemoryExtractionConfig = (config_id: number | string) => {
@@ -132,9 +172,30 @@ export const updateMemoryExtractionConfig = (values: ExtractionConfigForm) => {
return request.post('/memory-storage/update_config_extracted', values)
}
// 记忆萃取引擎-试运行
export const pilotRunMemoryExtractionConfig = (values: { config_id: number | string; dialogue_text: string }) => {
return request.post('/memory-storage/pilot_run', values)
export const pilotRunMemoryExtractionConfig = (values: { config_id: number | string; dialogue_text: string; }, onMessage?: (data: SSEMessage[]) => void) => {
return handleSSE('/memory-storage/pilot_run', values, onMessage)
}
// 情绪引擎-获取配置
export const getMemoryEmotionConfig = (config_id: number | string) => {
return request.get('/memory/emotion/read_config', { config_id: config_id })
}
// 情绪引擎-更新配置
export const updateMemoryEmotionConfig = (values: EmotionConfig) => {
return request.post('/memory/emotion/updated_config', values)
}
// 反思引擎-获取配置
export const getMemoryReflectionConfig = (config_id: number | string) => {
return request.get('/memory/reflection/configs', { config_id: config_id })
}
// 反思引擎-更新配置
export const updateMemoryReflectionConfig = (values: SelfReflectionEngineConfig) => {
return request.post('/memory/reflection/save', values)
}
// 反思引擎-试运行
export const pilotRunMemoryReflectionConfig = (values: { config_id: number | string; language_type: string; }) => {
return request.get('/memory/reflection/run', values)
}
/*************** end 记忆管理 相关接口 ******************************/

17
web/src/api/order.ts Normal file
View File

@@ -0,0 +1,17 @@
import { request } from '@/utils/request'
import type { VoucherForm } from '@/views/OrderPayment/types'
export const getOrderListUrl = '/v1/orders/customer'
// 提交支付凭证API
export const submitPaymentVoucherAPI = (voucherData: VoucherForm) => {
return request.post('/v1/orders/', voucherData)
}
// 订单详情
export const getOrderDetail = (order_no: string) => {
return request.get(`/v1/orders/customer/${order_no}`)
}
export const orderStatusUrl = '/v1/order-status/'
export const getOrderStatus = () => {
return request.get(orderStatusUrl)
}

12
web/src/api/prompt.ts Normal file
View File

@@ -0,0 +1,12 @@
import { request } from '@/utils/request'
import type { AiPromptForm } from '@/views/ApplicationConfig/types'
export const createPromptSessions = () => {
return request.post(`/prompt/sessions`)
}
export const getPrompt = (session_id: string) => {
return request.get(`/prompt/sessions/${session_id}`)
}
export const updatePromptMessages = (session_id: string, data: AiPromptForm) => {
return request.post(`/prompt/sessions/${session_id}/messages`, data)
}

31
web/src/api/tools.ts Normal file
View File

@@ -0,0 +1,31 @@
import { request } from '@/utils/request'
import type { Query, CustomToolItem, ExecuteData, MCPToolItem, InnerToolItem } from '@/views/ToolManagement/types'
// 工具列表
export const getTools = (data: Query) => {
return request.get('/tools', data)
}
// 创建MCP工具
export const addTool = (values: MCPToolItem | CustomToolItem) => {
return request.post('/tools', values)
}
// 更新工具
export const updateTool = (tool_id: string, data: MCPToolItem | InnerToolItem | CustomToolItem) => {
return request.put(`/tools/${tool_id}`, data)
}
// 删除工具
export const deleteTool = (tool_id: string) => {
return request.delete(`/tools/${tool_id}`)
}
// MCP 测试连接
export const testConnection = (tool_id: string) => {
return request.post(`/tools/${tool_id}/test`)
}
// 工具测试
export const execute = (data: ExecuteData) => {
return request.post(`/tools/execution/execute`, data)
}
export const parseSchema = (data: Record<string, any>) => {
return request.post(`/tools/parse_schema`, data)
}

View File

@@ -1,5 +1,6 @@
import { request } from '@/utils/request'
import type { SpaceModalData } from '@/views/SpaceManagement/types'
import type { ConfigModalData } from '@/views/UserMemory/types'
// 空间列表
export const getWorkspaces = () => {
@@ -22,6 +23,6 @@ export const getWorkspaceModels = () => {
return request.get(`/workspaces/workspace_models`)
}
// 更新空间模型配置
export const updateWorkspaceModels = () => {
return request.post(`/workspaces/workspace_models`)
export const updateWorkspaceModels = (data: ConfigModalData) => {
return request.put(`/workspaces/workspace_models`, data)
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>全屏</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="红熊空间-记忆管理-个人记忆" transform="translate(-967, -349)" stroke="#5B6167">
<g id="图谱" transform="translate(432, 334)">
<g id="编组-18" transform="translate(535, 13)">
<g id="全屏" transform="translate(0, 2)">
<g id="编组-6" transform="translate(2, 2)">
<polyline id="路径" points="0 3 0 0 3 0"></polyline>
<polyline id="路径" transform="translate(10.5, 1.5) scale(-1, 1) translate(-10.5, -1.5)" points="9 3 9 0 12 0"></polyline>
<polyline id="路径" transform="translate(1.5, 10.5) scale(1, -1) translate(-1.5, -10.5)" points="0 12 0 9 3 9"></polyline>
<polyline id="路径" transform="translate(10.5, 10.5) scale(-1, -1) translate(-10.5, -10.5)" points="9 12 9 9 12 9"></polyline>
<line x1="5.30274338e-05" y1="0" x2="4.00960414" y2="4.02323899" id="路径-3"></line>
<line x1="12" y1="0" x2="8.01466597" y2="4.01783837" id="路径-4"></line>
<line x1="0" y1="11.9995998" x2="4.00871245" y2="8.01746225" id="路径-5"></line>
<line x1="11.9977093" y1="12" x2="8.01643138" y2="8.01746225" id="路径-7"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>全屏</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="红熊空间-记忆管理-个人记忆" transform="translate(-967, -349)" stroke="#212332">
<g id="图谱" transform="translate(432, 334)">
<g id="编组-18" transform="translate(535, 13)">
<g id="全屏" transform="translate(0, 2)">
<g id="编组-6" transform="translate(2, 2)">
<polyline id="路径" points="0 3 0 0 3 0"></polyline>
<polyline id="路径" transform="translate(10.5, 1.5) scale(-1, 1) translate(-10.5, -1.5)" points="9 3 9 0 12 0"></polyline>
<polyline id="路径" transform="translate(1.5, 10.5) scale(1, -1) translate(-1.5, -10.5)" points="0 12 0 9 3 9"></polyline>
<polyline id="路径" transform="translate(10.5, 10.5) scale(-1, -1) translate(-10.5, -10.5)" points="9 12 9 9 12 9"></polyline>
<line x1="5.30274338e-05" y1="0" x2="4.00960414" y2="4.02323899" id="路径-3"></line>
<line x1="12" y1="0" x2="8.01466597" y2="4.01783837" id="路径-4"></line>
<line x1="0" y1="11.9995998" x2="4.00871245" y2="8.01746225" id="路径-5"></line>
<line x1="11.9977093" y1="12" x2="8.01643138" y2="8.01746225" id="路径-7"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 16</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="红熊空间-记忆看板" transform="translate(-1372, -1136)" stroke="#212332">
<g id="快速操作" transform="translate(848, 1048)">
<g id="1" transform="translate(296, 68)">
<g id="编组-16" transform="translate(228, 20)">
<line x1="13.7142857" y1="2.28571429" x2="2.28571429" y2="13.7142857" id="路径-15"></line>
<polyline id="路径" points="5.55102041 2.28571429 13.7142857 2.28571429 13.7142857 10.4489796"></polyline>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>菜单-收费管理</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="首页-弹窗" transform="translate(-904, -514)" stroke="#5F6266">
<g id="编组-7" transform="translate(904, 314)">
<g id="菜单-收费管理" transform="translate(0, 200)">
<g id="编组-8" transform="translate(2, 2)">
<polyline id="路径" points="7.5 5.80029489 6 7.30029489 4.5 5.80029489"></polyline>
<path d="M9.5,2 L10.5,2 C11.3284271,2 12,2.67157288 12,3.5 L12,10.5 C12,11.3284271 11.3284271,12 10.5,12 L1.5,12 C0.671572875,12 0,11.3284271 0,10.5 L0,3.5 C0,2.67157288 0.671572875,2 1.5,2 L2.5,2 L2.5,2" id="路径"></path>
<path d="M2.5,2.5 L2.5,1 C2.5,0.44771525 2.94771525,1.11022302e-16 3.5,0 L8.5,0 C9.05228475,0 9.5,0.44771525 9.5,1 L9.5,2.5 L9.5,2.5" id="路径"></path>
<line x1="0" y1="4.01402447" x2="12" y2="4.01402447" id="路径-4"></line>
<line x1="4.01045351" y1="1.8" x2="8.01045351" y2="1.8" id="路径-5"></line>
<line x1="6" y1="7.22565966" x2="6" y2="10.0211794" id="路径-6"></line>
<line x1="4.5" y1="7.70544887" x2="7.5" y2="7.70544887" id="路径-7"></line>
<line x1="4.5" y1="9.20544887" x2="7.5" y2="9.20544887" id="路径-7"></line>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>菜单-收费管理</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="首页-弹窗" transform="translate(-975, -514)" stroke="#212332">
<g id="编组-7" transform="translate(975, 314)">
<g id="菜单-收费管理" transform="translate(0, 200)">
<g id="编组-8" transform="translate(2, 2)">
<polyline id="路径" points="7.5 5.80029489 6 7.30029489 4.5 5.80029489"></polyline>
<path d="M9.5,2 L10.5,2 C11.3284271,2 12,2.67157288 12,3.5 L12,10.5 C12,11.3284271 11.3284271,12 10.5,12 L1.5,12 C0.671572875,12 0,11.3284271 0,10.5 L0,3.5 C0,2.67157288 0.671572875,2 1.5,2 L2.5,2 L2.5,2" id="路径"></path>
<path d="M2.5,2.5 L2.5,1 C2.5,0.44771525 2.94771525,1.11022302e-16 3.5,0 L8.5,0 C9.05228475,0 9.5,0.44771525 9.5,1 L9.5,2.5 L9.5,2.5" id="路径"></path>
<line x1="0" y1="4.01402447" x2="12" y2="4.01402447" id="路径-4"></line>
<line x1="4.01045351" y1="1.8" x2="8.01045351" y2="1.8" id="路径-5"></line>
<line x1="6" y1="7.22565966" x2="6" y2="10.0211794" id="路径-6"></line>
<line x1="4.5" y1="7.70544887" x2="7.5" y2="7.70544887" id="路径-7"></line>
<line x1="4.5" y1="9.20544887" x2="7.5" y2="9.20544887" id="路径-7"></line>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 29</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="红熊空间-应用管理" transform="translate(-24, -249)" stroke="#212332">
<g id="记忆库" transform="translate(12, 241)">
<g id="编组-29" transform="translate(12, 8)">
<g id="编组-30" transform="translate(1.5, 2)">
<path d="M5.15208739,12 L3.96504286,11.9873871 C3.14287934,11.9786512 2.48098017,11.3096817 2.48098017,10.4874718 L2.48098017,8.17573134 L2.48098017,8.17573134 L0.701703053,7.96314675 C0.482349244,7.93693878 0.325773604,7.73787163 0.351981572,7.51851782 C0.360586526,7.44649662 0.388612869,7.37817062 0.433061291,7.3208519 L1.79815052,5.56049306 L1.79815052,5.56049306 L1.79815052,5.43150782 C1.79815052,2.43176888 4.30576994,0 7.39907526,0 C10.4923806,0 13,2.43176888 13,5.43150782" id="路径" stroke-linecap="round"></path>
<path d="M8,6 L11,6 C11.5522847,6 12,6.44771525 12,7 L12,11 C12,11.5522847 11.5522847,12 11,12 L8,12 C7.44771525,12 7,11.5522847 7,11 L7,7 C7,6.44771525 7.44771525,6 8,6 Z" id="矩形"></path>
<line x1="7" y1="8" x2="12" y2="8" id="路径-26"></line>
<line x1="7" y1="10" x2="12" y2="10" id="路径-26备份"></line>
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>注意</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="收费管理" transform="translate(-280, -839)" fill="#FF5D34" fill-rule="nonzero">
<g id="编组-10" transform="translate(264, 823)">
<g id="注意" transform="translate(16, 16)">
<path d="M8.7755948,1.95407174 C9.31341765,1.01530942 10.6865824,1.01530942 11.2358482,1.95407174 L19.8066846,16.6349572 C20.3559505,17.5737195 19.6693682,18.7386896 18.5708364,18.75 L1.42916361,18.75 C0.330631841,18.75 -0.355950514,17.5737195 0.19331537,16.6349572 Z M10,15 C9.48223305,15 9.0625,15.419733 9.0625,15.9375 C9.0625,16.455267 9.48223305,16.875 10,16.875 C10.517767,16.875 10.9375,16.455267 10.9375,15.9375 C10.9375,15.419733 10.517767,15 10,15 Z M10,6.25 C9.48223305,6.25 9.0625,6.66973305 9.0625,7.1875 L9.0625,12.8125 C9.0625,13.330267 9.48223305,13.75 10,13.75 C10.517767,13.75 10.9375,13.330267 10.9375,12.8125 L10.9375,7.1875 C10.9375,6.66973305 10.517767,6.25 10,6.25 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>对号备份</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="定价-支付" transform="translate(-941, -357)" fill="#5B6167" fill-rule="nonzero">
<g id="编组-8" transform="translate(360, 219)">
<g id="编组-2" transform="translate(581, 134)">
<g id="对号备份" transform="translate(0, 4)">
<path d="M6,0 C10,0 12,2 12,6 C12,10 10,12 6,12 C2,12 0,10 0,6 C0,2 2,0 6,0 Z M8.96637129,3.91456632 C8.74659858,3.69499924 8.39044332,3.69516594 8.17087624,3.91493864 L5.06113614,7.0275 L3.85141273,5.81979618 C3.63162085,5.60024831 3.2754656,5.60044612 3.05591773,5.82023801 C2.83636985,6.04002989 2.83656766,6.39618514 3.05635955,6.61573301 L4.6637238,8.22131277 C4.88350211,8.44084708 5.23963056,8.44066484 5.45918407,8.22090572 L8.9667436,4.71006136 C9.18631068,4.49028866 9.18614399,4.1341334 8.96637129,3.91456632 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>企业_画板 39@2x</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="定价-支付" transform="translate(-404, -623)" fill="#5B6167" fill-rule="nonzero">
<g id="编组-9" transform="translate(360, 572)">
<g id="编组-13" transform="translate(24, 28)">
<g id="企业_画板-39" transform="translate(20, 23)">
<path d="M22.6288053,2.37305419 C23.0204473,2.23747924 23.3145868,2.25900923 23.6919502,2.44276018 C24.0617664,2.62288728 25.6534241,3.72773786 25.9496034,4.01380953 C26.0658721,4.12614915 26.3712305,4.58595288 26.3712305,4.72515169 L26.3712305,28.0018359 C26.4530266,28.0862505 27.1973505,27.7436893 27.3764451,28.1371977 C27.4557934,28.3119956 27.4672163,29.6287353 27.2467137,29.6287353 L21.8000336,29.6287353 C21.7818794,29.6287353 21.6233867,29.5236434 21.6056404,29.4931604 L21.5446504,5.02657147 C21.4958991,4.86989287 21.331899,4.74412365 21.166879,4.77098284 C21.0187893,4.79507085 20.3731919,5.12974486 20.1677838,5.2218335 C18.029459,6.17981122 15.6408506,7.2130373 13.5747348,8.29294721 C13.376262,8.3967601 13.2344958,8.39505476 13.2049187,8.65192239 C13.1657544,8.99022026 13.1547395,10.3811425 13.2102221,10.6870388 C13.2300082,10.7957546 13.3515804,10.9596809 13.449083,10.9831294 C13.6673418,11.0353556 15.7193829,10.1487892 16.1403981,10.0260044 C16.2782888,9.98571558 16.4594232,9.91110672 16.5977218,9.92389681 C16.9036922,9.95203501 18.7858177,11.1775388 19.1684847,11.4395224 C19.2908728,11.5232975 19.6443705,11.7298575 19.680475,11.8532818 L19.7406492,27.6469109 C19.9835897,28.1139624 20.7572867,27.6294311 20.9818689,28.04383 C21.1122123,28.2842837 21.1334262,29.458414 20.8600927,29.5944153 L15.2826613,29.5950548 L15.1829149,29.4283573 L15.1563975,12.6505308 C15.1329398,12.3595562 15.1168253,12.2231286 14.7978003,12.2116175 L7.01432266,14.8538369 L6.69162593,15.0586915 L6.6579692,29.3893475 C6.65144183,29.4910287 6.60044677,29.537073 6.52640195,29.5907914 C6.29610827,29.7579152 4.81786354,29.7711317 4.64346045,29.5325965 C4.54534596,29.3987269 4.56003253,28.3989682 4.61306739,28.1992296 C4.68996794,27.9097473 5.17544089,28.12995 5.23622699,27.6326287 L5.23459515,14.2139061 L5.32414247,14.0365502 L11.5253454,11.7079012 C11.6140768,11.6079253 11.6389624,11.4994227 11.653445,11.3674716 C11.7713456,10.2926777 11.5033156,8.89003123 11.6560968,7.84657308 C11.6811863,7.67582538 11.7862362,7.47629998 11.9263706,7.38357183 Z M13.1298539,26.7341249 C13.41767,26.7230401 13.7566852,26.6450206 13.8427648,27.033413 C13.8603581,27.1127115 13.87035,27.429576 13.8728157,27.8021561 L13.8727754,28.1865728 C13.8693843,28.7059304 13.8526579,29.2165747 13.8227748,29.2874531 C13.7956454,29.3518299 13.7146652,29.411517 13.6606105,29.4569218 C12.4008286,29.6148794 11.1332954,29.5374994 9.86617025,29.557324 C9.31134403,29.5660639 8.69348791,29.6911936 8.11398008,29.632146 C7.95915908,29.6163716 7.80291023,29.5944153 7.73049725,29.423028 C7.65910416,29.2537725 7.6701191,27.3030707 7.7443679,27.1365863 C7.77170125,27.0754071 7.80291023,27.0187044 7.8692038,26.9961085 C9.62119,26.9070042 11.3768478,26.8021255 13.1298539,26.734338 Z M13.6149189,22.525759 C13.8127797,22.624669 13.840929,22.7357297 13.8605111,22.949111 C13.902531,23.408062 13.902939,24.395457 13.8564315,24.85185 C13.8466405,24.9490547 13.8360335,25.1031753 13.748322,25.1609438 L7.69643255,26.0366386 C7.79169131,25.3491713 7.56833296,24.2673429 7.69643255,23.6310359 C7.70561166,23.5860575 7.77272115,23.5152856 7.79413908,23.4619936 Z M13.5565806,18.2619694 C13.7026304,18.3067348 13.8248146,18.3877387 13.8552076,18.5499596 C13.9153818,18.8720567 13.8951878,20.217361 13.8603071,20.5855024 C13.851128,20.6816413 13.8684663,20.7790591 13.7964613,20.8577182 L13.5476054,21.0041647 L7.69663653,22.3096065 L7.69663653,20.0395787 C7.69663653,20.0163434 7.79638286,19.8671257 7.8290197,19.8392007 Z M13.5115009,13.9915717 C13.6646901,13.9862425 13.8266504,14.1254413 13.8554116,14.2808409 C13.9096703,14.5739471 13.9006952,16.0603687 13.8598992,16.3835316 C13.8480683,16.4779651 13.8456206,16.5600348 13.7634165,16.6216404 L7.99179592,18.5844929 C7.73090521,18.5465489 7.71581067,18.222107 7.70030817,18.0093652 C7.671139,17.60733 7.60851707,16.2677813 7.89164163,16.041823 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>订单</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="收费管理" transform="translate(-1266, -130)" fill-rule="nonzero">
<g id="编组-5" transform="translate(264, 88)">
<g id="编组-9" transform="translate(986, 32)">
<g id="订单" transform="translate(16, 10)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M12.8333281,4 C12.8333281,3.72385763 13.0571858,3.5 13.3333281,3.5 C13.6094705,3.5 13.8333281,3.72385763 13.8333281,4 L13.8333281,12.8333281 C13.8333281,13.8458281 13.0125,14.6666719 12,14.6666719 L4,14.6666719 C2.9875,14.6666719 2.16667188,13.8458281 2.16667188,12.8333281 L2.16667188,3.16667188 C2.16667188,2.15417187 2.9875,1.33332812 4,1.33332812 L13.3333281,1.33332812 C13.6094705,1.33332812 13.8333281,1.55718575 13.8333281,1.83332812 C13.8333281,2.1094705 13.6094705,2.33332812 13.3333281,2.33332812 L4,2.33332812 C3.77898493,2.33332812 3.5670223,2.42112709 3.41074251,2.57740981 C3.25446272,2.73369252 3.16667188,2.94565681 3.16667188,3.16667188 L3.16667188,12.8333281 C3.16667188,13.0543432 3.25446272,13.2663075 3.41074251,13.4225902 C3.5670223,13.5788729 3.77898493,13.6666719 4,13.6666719 L12,13.6666719 C12.2210151,13.6666719 12.4329777,13.5788729 12.5892575,13.4225902 C12.7455373,13.2663075 12.8333323,13.0543432 12.8333281,12.8333281 L12.8333281,4 Z M5.33332812,6.83332812 C5.05718575,6.83332812 4.83332812,6.6094705 4.83332812,6.33332812 C4.83332812,6.05718575 5.05718575,5.83332812 5.33332812,5.83332812 L10.6666719,5.83332812 C10.9428142,5.83332812 11.1666719,6.05718575 11.1666719,6.33332812 C11.1666719,6.6094705 10.9428142,6.83332812 10.6666719,6.83332812 L5.33332812,6.83332812 Z M5.33332812,9.5 C5.05718575,9.5 4.83332812,9.27614237 4.83332812,9 C4.83332812,8.72385763 5.05718575,8.5 5.33332812,8.5 L8.66667188,8.5 C8.94281425,8.5 9.16667188,8.72385763 9.16667188,9 C9.16667188,9.27614237 8.94281425,9.5 8.66667188,9.5 L5.33332812,9.5 Z" id="形状" stroke="#212332" stroke-width="0.4" fill="#212332"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>订单</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="收费管理" transform="translate(-1266, -130)" fill-rule="nonzero">
<g id="编组-5" transform="translate(264, 88)">
<g id="编组-9" transform="translate(986, 32)">
<g id="订单" transform="translate(16, 10)">
<rect id="矩形" fill="#155EEF" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M12.8333281,4 C12.8333281,3.72385763 13.0571858,3.5 13.3333281,3.5 C13.6094705,3.5 13.8333281,3.72385763 13.8333281,4 L13.8333281,12.8333281 C13.8333281,13.8458281 13.0125,14.6666719 12,14.6666719 L4,14.6666719 C2.9875,14.6666719 2.16667188,13.8458281 2.16667188,12.8333281 L2.16667188,3.16667188 C2.16667188,2.15417187 2.9875,1.33332812 4,1.33332812 L13.3333281,1.33332812 C13.6094705,1.33332812 13.8333281,1.55718575 13.8333281,1.83332812 C13.8333281,2.1094705 13.6094705,2.33332812 13.3333281,2.33332812 L4,2.33332812 C3.77898493,2.33332812 3.5670223,2.42112709 3.41074251,2.57740981 C3.25446272,2.73369252 3.16667188,2.94565681 3.16667188,3.16667188 L3.16667188,12.8333281 C3.16667188,13.0543432 3.25446272,13.2663075 3.41074251,13.4225902 C3.5670223,13.5788729 3.77898493,13.6666719 4,13.6666719 L12,13.6666719 C12.2210151,13.6666719 12.4329777,13.5788729 12.5892575,13.4225902 C12.7455373,13.2663075 12.8333323,13.0543432 12.8333281,12.8333281 L12.8333281,4 Z M5.33332812,6.83332812 C5.05718575,6.83332812 4.83332812,6.6094705 4.83332812,6.33332812 C4.83332812,6.05718575 5.05718575,5.83332812 5.33332812,5.83332812 L10.6666719,5.83332812 C10.9428142,5.83332812 11.1666719,6.05718575 11.1666719,6.33332812 C11.1666719,6.6094705 10.9428142,6.83332812 10.6666719,6.83332812 L5.33332812,6.83332812 Z M5.33332812,9.5 C5.05718575,9.5 4.83332812,9.27614237 4.83332812,9 C4.83332812,8.72385763 5.05718575,8.5 5.33332812,8.5 L8.66667188,8.5 C8.94281425,8.5 9.16667188,8.72385763 9.16667188,9 C9.16667188,9.27614237 8.94281425,9.5 8.66667188,9.5 L5.33332812,9.5 Z" id="形状" stroke="#155EEF" stroke-width="0.4" fill="#155EEF"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -2,7 +2,7 @@
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>刷新</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="应用管理--API" transform="translate(-1071, -490)" fill="#212332" fill-rule="nonzero">
<g id="应用管理--API" transform="translate(-1071, -490)" fill="#5B6167" fill-rule="nonzero">
<g id="2" transform="translate(220, 336)">
<g id="主操作" transform="translate(839, 144)">
<g id="刷新" transform="translate(12, 10)">

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>刷新</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="应用管理--API" transform="translate(-1071, -490)" fill="#155EEF" fill-rule="nonzero">
<g id="2" transform="translate(220, 336)">
<g id="主操作" transform="translate(839, 144)">
<g id="刷新" transform="translate(12, 10)">
<path d="M14.5,6.60760714 L14.5,3.35760714 L13.4384813,4.41757143 C12.2346397,2.59629629 10.195406,1.50029795 8.00999678,1.5 C4.41487535,1.5 1.5,4.41014286 1.5,8.00046429 C1.5,11.5907857 4.41487535,14.5009287 8.00999678,14.5009287 C10.6602392,14.5014558 13.046297,12.8977788 14.0434028,10.4458571 C14.1184045,10.261489 14.0892053,10.0511714 13.9668045,9.89412914 C13.8444036,9.73708685 13.6473966,9.65717825 13.4499941,9.68450414 C13.2525917,9.71183003 13.0847838,9.84223896 13.0097822,10.0266071 C12.1832078,12.0581943 10.2060581,13.38691 8.00999678,13.3866429 C5.03095604,13.3866429 2.61591974,10.9751429 2.61591974,8.00046429 C2.61591974,5.02578571 5.03095604,2.61428571 8.00999678,2.61428571 C9.93449337,2.61428571 11.6706785,3.63107143 12.6308344,5.22357143 L11.2452341,6.60760714 L14.5,6.60760714 Z" id="路径"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Some files were not shown because too many files have changed in this diff Show More