Просмотр исходного кода

Merge branch 'main' into dev_tao

guantao 1 неделя назад
Родитель
Сommit
57e53b3355

+ 0 - 2
agent/tools/builtin/__init__.py

@@ -17,7 +17,6 @@ from agent.tools.builtin.skill import skill, list_skills
 from agent.tools.builtin.subagent import agent, evaluate
 from agent.tools.builtin.experience import get_experience
 from agent.tools.builtin.search import search_posts, get_search_suggestions
-from agent.tools.builtin.nanobanana import nanobanana_extract_features
 from agent.tools.builtin.sandbox import (sandbox_create_environment, sandbox_run_shell,
                                          sandbox_rebuild_with_ports,sandbox_destroy_environment)
 
@@ -42,7 +41,6 @@ __all__ = [
     "evaluate",
     "search_posts",
     "get_search_suggestions",
-    "nanobanana_extract_features",
     "sandbox_create_environment",
     "sandbox_run_shell",
     "sandbox_rebuild_with_ports",

+ 5 - 0
examples/how/run.py

@@ -39,6 +39,7 @@ from agent.trace import (
     Message,
 )
 from agent.llm import create_openrouter_llm_call
+from agent.tools import get_tool_registry
 
 
 # ===== 非阻塞 stdin 检测 =====
@@ -294,6 +295,10 @@ async def main():
     print(f"   - Skills 目录: {skills_dir}")
     print(f"   - 模型: {prompt.config.get('model', 'sonnet-4.5')}")
 
+    # 加载自定义工具
+    print("   - 加载自定义工具: nanobanana")
+    import examples.how.tool  # 导入自定义工具模块,触发 @tool 装饰器注册
+
     store = FileSystemTraceStore(base_path=".trace")
     runner = AgentRunner(
         trace_store=store,

+ 5 - 0
examples/how/skills/construct.md

@@ -9,6 +9,11 @@ description: 建构社交媒体帖子内容
 
 ---
 
+## 主要工具
+你可以使用 `nanobanana` 工具生成图片。
+
+---
+
 ## 输出
 
 将最终的生成内容组织到输出文件夹中。不同版本的输出应该分别是一个子文件夹。

+ 7 - 0
examples/how/tool/__init__.py

@@ -0,0 +1,7 @@
+"""
+How 示例的自定义工具
+"""
+
+from examples.how.tool.nanobanana import nanobanana
+
+__all__ = ["nanobanana"]

+ 1 - 1
agent/tools/builtin/nanobanana.py → examples/how/tool/nanobanana.py

@@ -215,7 +215,7 @@ def _normalize_model_id(model_id: str) -> str:
 
 
 @tool(description="可以提取图片中的特征,也可以根据描述生成图片")
-async def nanobanana_extract_features(
+async def nanobanana(
     image_path: str = "",
     image_paths: Optional[List[str]] = None,
     output_file: Optional[str] = None,

+ 29 - 1
frontend/react-template/src/components/MainContent/MainContent.module.css

@@ -26,10 +26,38 @@
 }
 
 .status {
-  font-size: 12px;
+  display: flex;
+  align-items: center;
+  gap: var(--space-xs);
+  font-size: 13px;
+  font-weight: 500;
+  transition: all 200ms ease-in-out;
+}
+
+.statusDot {
+  width: 6px;
+  height: 6px;
+  border-radius: var(--radius-full);
+  transition: background-color 200ms ease-in-out;
+}
+
+.status.connected {
+  color: var(--color-success);
+}
+
+.status.connected .statusDot {
+  background-color: var(--color-success);
+  box-shadow: 0 0 8px var(--color-success);
+}
+
+.status.disconnected {
   color: var(--text-tertiary);
 }
 
+.status.disconnected .statusDot {
+  background-color: var(--border-medium);
+}
+
 .headerRight {
   display: flex;
   align-items: center;

+ 18 - 2
frontend/react-template/src/components/MainContent/MainContent.tsx

@@ -18,6 +18,22 @@ interface MainContentProps {
   messageRefreshTrigger?: number;
 }
 
+interface ConnectionStatusProps {
+  isConnected: boolean;
+}
+
+const ConnectionStatus: FC<ConnectionStatusProps> = ({ isConnected }) => {
+  return (
+    <div
+      className={`${styles.status} ${isConnected ? styles.connected : styles.disconnected}`}
+      data-testid="connection-status"
+    >
+      <span className={styles.statusDot} />
+      <span>{isConnected ? "WebSocket 已连接" : "WebSocket 未连接"}</span>
+    </div>
+  );
+};
+
 export const MainContent: FC<MainContentProps> = ({
   traceId,
   onNodeClick,
@@ -83,7 +99,7 @@ export const MainContent: FC<MainContentProps> = ({
       <div className={styles.main}>
         <div className={styles.header}>
           <div className={styles.title}>暂无 Trace</div>
-          <div className={styles.status}>未连接</div>
+          <ConnectionStatus isConnected={connected} />
         </div>
         <div className={styles.content}>
           <div className={styles.empty}>暂无可展示的数据</div>
@@ -95,7 +111,7 @@ export const MainContent: FC<MainContentProps> = ({
   return (
     <div className={styles.main}>
       <div className={styles.header}>
-        <div className={styles.title}>{connected ? "WebSocket 已连接" : "WebSocket 未连接"}</div>
+        <ConnectionStatus isConnected={connected} />
         <div className={styles.headerRight}>
           <Select
             value={traceId}

+ 6 - 6
frontend/react-template/src/components/TopBar/TopBar.tsx

@@ -261,7 +261,7 @@ export const TopBar: FC<TopBarProps> = ({
         </button>
       </div>
       <Modal
-        title="新建任务"
+        title={<div className="w-full text-center">新建任务</div>}
         visible={isModalVisible}
         onOk={handleConfirm}
         onCancel={() => setIsModalVisible(false)}
@@ -285,7 +285,7 @@ export const TopBar: FC<TopBarProps> = ({
         </Form>
       </Modal>
       <Modal
-        title="插入指令"
+        title={<div className="w-full text-center">插入指令</div>}
         visible={isInsertModalVisible}
         onOk={handleInsertConfirm}
         onCancel={() => setIsInsertModalVisible(false)}
@@ -296,14 +296,14 @@ export const TopBar: FC<TopBarProps> = ({
         <Form getFormApi={(api: any) => (insertFormApiRef.current = api)}>
           <Form.TextArea
             field="insert_prompt"
-            label="指令"
+            label=" "
             placeholder="请输入插入指令"
             autosize={{ minRows: 3, maxRows: 6 }}
           />
         </Form>
       </Modal>
       <Modal
-        title="反思"
+        title={<div className="w-full text-center">反思</div>}
         visible={isReflectModalVisible}
         onOk={handleReflectConfirm}
         onCancel={() => setIsReflectModalVisible(false)}
@@ -314,14 +314,14 @@ export const TopBar: FC<TopBarProps> = ({
         <Form getFormApi={(api: any) => (reflectFormApiRef.current = api)}>
           <Form.TextArea
             field="reflect_focus"
-            label="反思重点"
+            label=" "
             placeholder="请输入反思重点(可选)"
             autosize={{ minRows: 3, maxRows: 6 }}
           />
         </Form>
       </Modal>
       <Modal
-        title="经验列表"
+        title={<div className="w-full text-center">经验列表</div>}
         visible={isExperienceModalVisible}
         onCancel={() => setIsExperienceModalVisible(false)}
         footer={null}