Преглед изворни кода

update librarian & visualization

guantao пре 3 месеци
родитељ
комит
f55c5ba6d3
47 измењених фајлова са 3347 додато и 252 уклоњено
  1. 42 0
      add_knowledge.py
  2. 44 0
      add_workflow_experience_knowledge.py
  3. 7 3
      agent/core/runner.py
  4. 30 15
      agent/tools/builtin/knowledge.py
  5. 2 2
      agent/tools/builtin/librarian.py
  6. 4 0
      cache/knowledge_pending/knowledge-2026-04-09-x-portrait-workflows.json
  7. 80 0
      direct_insert.py
  8. 13 0
      im-server/__main__.py
  9. 132 0
      insert_ai_workflow_capabilities.py
  10. 73 0
      insert_knowledge.py
  11. 91 0
      insert_knowledge_direct.py
  12. 1 0
      knowhub/agents/librarian.py
  13. 5 0
      knowhub/agents/librarian_agent.prompt
  14. 444 0
      knowhub/docs/knowledge-management.md
  15. 44 7
      knowhub/frontend/src/App.tsx
  16. 23 51
      knowhub/frontend/src/layouts/MainLayout.tsx
  17. 4 1
      knowhub/frontend/src/main.tsx
  18. 11 1
      knowhub/frontend/src/pages/Dashboard.tsx
  19. 133 163
      knowhub/frontend/src/pages/Relations.tsx
  20. 0 1
      knowhub/internal_tools/__init__.py
  21. 362 0
      knowhub/research/outdoor_character_workflow_summary_20260409.md
  22. 59 8
      knowhub/server.py
  23. 104 0
      knowledge-2026-04-09-ai-image-workflows.json
  24. 398 0
      knowledge/ComfyUI_画中画分阶段生成工作流.md
  25. 165 0
      knowledge/Midjourney_v7_Impasto_厚涂风格参数配置.md
  26. 261 0
      knowledge/Nano_Banana_多图融合_ComfyUI 工作流.md
  27. 20 0
      knowledge/knowledge-20260412-005015-nano-banana-image-urls.json
  28. 20 0
      knowledge/knowledge-20260412-010725-nano-banana-http503.json
  29. 3 0
      knowledge/knowledge-20260412-010726-nano-banana-multi-fusion.json
  30. 20 0
      knowledge/knowledge-20260412-014259-3abf.json
  31. 21 0
      knowledge/strategy/品类分组判断标准.json
  32. 20 0
      knowledge/strategy/粗工序因果推理方法.json
  33. 17 0
      knowledge/strategy/能力模块变体分析方法.json
  34. 9 0
      knowledge_batch_20260409.json
  35. 26 0
      knowledge_batch_20260409_execution.json
  36. 34 0
      knowledge_batch_20260410_workflow_analysis.json
  37. 51 0
      knowledge_batch_20260411_toolhub_error_recovery.json
  38. 9 0
      pending_uploads/2026-04-09_ai_image_local_detail_strategy.json
  39. 28 0
      pending_uploads/knowledge_batch_20260410_file_search.json
  40. 81 0
      pre_upload_list.json
  41. 69 0
      process_upload.py
  42. 0 0
      test-dir/__main__.py
  43. 72 0
      test_upload.py
  44. 80 0
      test_upload_sync.py
  45. 18 0
      tools/image_gen/nano_banana.json
  46. 18 0
      tools/upload/image_uploader.json
  47. 199 0
      wf_004_intents.json

+ 42 - 0
add_knowledge.py

@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+import json
+
+# 读取现有文件
+with open('/root/Agent/pre_upload_list.json', 'r', encoding='utf-8') as f:
+    data = json.load(f)
+
+# 新的知识条目
+new_knowledge = [
+    {
+        "task": "在图生图任务中,当 nano_banana 工具无法处理本地路径时的降级策略",
+        "content": "当使用 nano_banana 进行图生图时,如果传入本地图片路径(如 examples/xxx.png)会报错「Base64 decoding failed」,因为 nano_banana 期望的是 Base64 编码或网络 URL。\n\n正确做法:\n1. 先用 image_uploader 将本地图片上传获取 CDN URL\n2. 或使用支持本地路径自动上传的工具(如 flux_generate、seedream_generate)\n\n工具选择优先级:\n- nano_banana:需要手动上传获取 CDN URL,但支持多图融合,效果最好\n- flux_generate:自动处理本地路径,图生图效果好,推荐作为备选\n- seedream_generate:自动处理本地路径,需要最小尺寸 3686400 像素\n\n案例:Stage 4 检查 img_3 花丛色彩时,nano_banana 传入本地路径报错,切换至 flux_generate 成功完成修复。",
+        "types": ["experience"],
+        "tags": {
+            "intent": "图生图/色彩修复",
+            "state": "nano_banana/flux_generate"
+        },
+        "score": 4,
+        "source": {"category": "research"}
+    },
+    {
+        "task": "在图生图色彩控制中,通过 prompt 明确指定色彩排除可有效降低干扰元素饱和度",
+        "content": "当需要降低画面中某元素的色彩饱和度或改变其色系时,在 prompt 中明确指定:\n- 目标色彩:\"should be green foliage plants or very subtle low-saturation green flowers\"\n- 排除色彩:\"not purple or blue\"\n- 整体色调:\"The overall color scheme should be white and green with minimal color accents\"\n\n配合 flux-kontext-max 模型的图生图模式,可有效实现色彩调整而不破坏原有构图。\n\n案例:img_3 花丛色彩修复,将蓝紫色花丛成功调整为淡紫色/白色,融入绿色背景。",
+        "types": ["experience"],
+        "tags": {
+            "intent": "色彩控制/图生图优化",
+            "state": "flux_generate"
+        },
+        "score": 4,
+        "source": {"category": "research"}
+    }
+]
+
+# 添加到 knowledge 数组
+data['knowledge'].extend(new_knowledge)
+
+# 写回文件
+with open('/root/Agent/pre_upload_list.json', 'w', encoding='utf-8') as f:
+    json.dump(data, f, ensure_ascii=False, indent=2)
+
+print(f"成功添加 {len(new_knowledge)} 条知识条目")
+print(f"当前 knowledge 总数:{len(data['knowledge'])}")

+ 44 - 0
add_workflow_experience_knowledge.py

@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+import json
+
+# 读取现有文件
+with open('/root/Agent/pre_upload_list.json', 'r', encoding='utf-8') as f:
+    data = json.load(f)
+
+# 新的知识条目
+new_knowledge = [
+    {
+        "task": "在分析工作流文件时,当 glob 搜索找不到目标文件的有效定位策略",
+        "content": "当 glob_files 按模式匹配找不到目标文件时,应该:1) 使用 bash_command 执行 find 命令进行更广泛的文件系统搜索,支持通配符和路径遍历;2) 结合 grep 内容搜索,通过文件内容关键词定位(如工具名、方案名);3) 检查相关目录结构,如 research/outputs/ 下的子目录。案例:本次任务中 glob_files 搜索\"*wf_005*\"未找到文件,但通过 find 命令搜索包含\"ComfyUI\"\"Nano Banana\"等关键词的 markdown 文件,成功在 outputs/04/ 目录下找到颗粒感纸张纹理插画风格工作流调研报告.md,该文件即为 wf_005 的 raw_markdown 内容。",
+        "types": ["experience"],
+        "tags": {
+            "intent": "工作流文件定位",
+            "state": "文件搜索",
+            "tools": "glob_files,bash_command"
+        },
+        "score": 4,
+        "source": {"category": "research"}
+    },
+    {
+        "task": "工作流意图归纳的粒度判断原则",
+        "content": "在将工作流技术步骤归纳为意图级描述时,粒度判断应遵循:1) 保留工具差异——相同意图但不同工具实现应分开记录,因为工具选择是工作流变体的关键信息;2) 合并连续子步骤——同一工具内的连续操作(如新建图层→填充→设置混合模式)可合并为一个意图;3) 跨方案可比较——意图描述应使不同工作流的相同功能模块可对比。案例:本次将 Photoshop 的 5 种颗粒添加方法分别记录为\"添加溶解颗粒效果\"\"手绘颗粒纹理\"\"滤镜添加全局杂色\"等,保留了方法差异;而将 ComfyUI 的分割 + 重排合并为\"创建四方连续布局\"。",
+        "types": ["experience"],
+        "tags": {
+            "intent": "工作流分析",
+            "state": "意图归纳",
+            "method": "语义聚类"
+        },
+        "score": 4,
+        "source": {"category": "research"}
+    }
+]
+
+# 添加到 knowledge 数组
+data['knowledge'].extend(new_knowledge)
+
+# 写回文件
+with open('/root/Agent/pre_upload_list.json', 'w', encoding='utf-8') as f:
+    json.dump(data, f, ensure_ascii=False, indent=2)
+
+print(f"成功添加 {len(new_knowledge)} 条知识条目")
+print(f"当前 knowledge 总数:{len(data['knowledge'])}")

+ 7 - 3
agent/core/runner.py

@@ -104,6 +104,7 @@ class RunConfig:
     temperature: float = 0.3
     temperature: float = 0.3
     max_iterations: int = 200
     max_iterations: int = 200
     tools: Optional[List[str]] = None          # None = 全部已注册工具
     tools: Optional[List[str]] = None          # None = 全部已注册工具
+    exclude_tools: List[str] = field(default_factory=list)  # 从工具列表中排除的工具名
     side_branch_max_turns: int = 5             # 侧分支最大轮次(压缩/反思)
     side_branch_max_turns: int = 5             # 侧分支最大轮次(压缩/反思)
     goal_compression: Literal["none", "on_complete", "on_overflow"] = "on_overflow"  # Goal 压缩模式
     goal_compression: Literal["none", "on_complete", "on_overflow"] = "on_overflow"  # Goal 压缩模式
 
 
@@ -558,7 +559,7 @@ class AgentRunner:
         task_name = config.name or await self._generate_task_name(messages)
         task_name = config.name or await self._generate_task_name(messages)
 
 
         # 准备工具 Schema
         # 准备工具 Schema
-        tool_schemas = self._get_tool_schemas(config.tools)
+        tool_schemas = self._get_tool_schemas(config.tools, config.exclude_tools)
 
 
         trace_obj = Trace(
         trace_obj = Trace(
             trace_id=trace_id,
             trace_id=trace_id,
@@ -1092,7 +1093,7 @@ class AgentRunner:
     ) -> AsyncIterator[Union[Trace, Message]]:
     ) -> AsyncIterator[Union[Trace, Message]]:
         """ReAct 循环"""
         """ReAct 循环"""
         trace_id = trace.trace_id
         trace_id = trace.trace_id
-        tool_schemas = self._get_tool_schemas(config.tools)
+        tool_schemas = self._get_tool_schemas(config.tools, config.exclude_tools)
 
 
         # 当前主路径头节点的 sequence(用于设置 parent_sequence)
         # 当前主路径头节点的 sequence(用于设置 parent_sequence)
         head_seq = trace.head_sequence
         head_seq = trace.head_sequence
@@ -2844,12 +2845,13 @@ class AgentRunner:
         )
         )
         return messages
         return messages
 
 
-    def _get_tool_schemas(self, tools: Optional[List[str]]) -> List[Dict]:
+    def _get_tool_schemas(self, tools: Optional[List[str]], exclude_tools: Optional[List[str]] = None) -> List[Dict]:
         """
         """
         获取工具 Schema
         获取工具 Schema
 
 
         - tools=None: 使用 registry 中全部已注册工具(含内置 + 外部注册的)
         - tools=None: 使用 registry 中全部已注册工具(含内置 + 外部注册的)
         - tools=["a", "b"]: 在 BUILTIN_TOOLS 基础上追加指定工具
         - tools=["a", "b"]: 在 BUILTIN_TOOLS 基础上追加指定工具
+        - exclude_tools=["x"]: 从最终列表中排除指定工具(防止递归调用等场景)
         """
         """
         if tools is None:
         if tools is None:
             # 全部已注册工具
             # 全部已注册工具
@@ -2860,6 +2862,8 @@ class AgentRunner:
             for t in tools:
             for t in tools:
                 if t not in tool_names:
                 if t not in tool_names:
                     tool_names.append(t)
                     tool_names.append(t)
+        if exclude_tools:
+            tool_names = [t for t in tool_names if t not in exclude_tools]
         return self.tools.get_schemas(tool_names)
         return self.tools.get_schemas(tool_names)
 
 
     # 默认 system prompt 前缀(当 config.system_prompt 和前端都未提供 system message 时使用)
     # 默认 system prompt 前缀(当 config.system_prompt 和前端都未提供 system message 时使用)

+ 30 - 15
agent/tools/builtin/knowledge.py

@@ -1,18 +1,7 @@
 """
 """
 知识管理工具 - KnowHub API 封装
 知识管理工具 - KnowHub API 封装
 
 
-⚠️ DEPRECATED: 这些工具直接调用 KnowHub HTTP API,绕过 Knowledge Manager。
-推荐使用新架构:
-- knowledge_search → ask_knowledge (通过 IM 与 Knowledge Manager 交互)
-- knowledge_save → upload_knowledge (通过 IM 与 Knowledge Manager 交互)
-
-新架构优势:
-1. 统一通过 Knowledge Manager 管理知识
-2. 自动去重和整合
-3. 支持增量上传和批量提交
-4. 更好的并发控制
-
-所有工具通过 HTTP API 调用 KnowHub Server。
+所有工具通过 HTTP API 调用 KnowHub Server,直接读写底层数据库。
 """
 """
 
 
 import os
 import os
@@ -113,8 +102,6 @@ async def knowledge_search(
     """
     """
     检索知识(两阶段:语义路由 + 质量精排)
     检索知识(两阶段:语义路由 + 质量精排)
 
 
-    ⚠️ DEPRECATED: 推荐使用 ask_knowledge 工具(通过 Knowledge Manager)
-
     Args:
     Args:
         query: 搜索查询(任务描述)
         query: 搜索查询(任务描述)
         top_k: 返回数量(默认 5)
         top_k: 返回数量(默认 5)
@@ -126,7 +113,6 @@ async def knowledge_search(
     Returns:
     Returns:
         相关知识列表
         相关知识列表
     """
     """
-    logger.warning("knowledge_search is deprecated. Use ask_knowledge instead.")
     try:
     try:
         params = {
         params = {
             "q": query,
             "q": query,
@@ -752,3 +738,32 @@ async def requirement_list(
         return ToolResult(title="✅ 需求列表获取成功", output=json.dumps(data, ensure_ascii=False, indent=2))
         return ToolResult(title="✅ 需求列表获取成功", output=json.dumps(data, ensure_ascii=False, indent=2))
     except Exception as e:
     except Exception as e:
         return ToolResult(title="❌ 需求列表失败", output=str(e), error=str(e))
         return ToolResult(title="❌ 需求列表失败", output=str(e), error=str(e))
+
+# ==================== Relation (关系表) 检索工具 ====================
+
+@tool(hidden_params=["context"])
+async def relation_search(
+    table_name: str,
+    filters: Optional[Dict[str, str]] = None,
+    limit: int = 100,
+    context: Optional[ToolContext] = None,
+) -> ToolResult:
+    """通用关系表检索工具
+    Args:
+        table_name: 关系表名 (如 capability_knowledge, tool_provider 等)
+        filters: 查询条件字典 (如 {"capability_id": "xxx"})
+        limit: 返回数量限制
+    """
+    try:
+        params = {"limit": limit}
+        if filters:
+            params.update(filters)
+        async with httpx.AsyncClient(timeout=30.0) as client:
+            res = await client.get(f"{KNOWHUB_API}/api/relation/{table_name}", params=params)
+            res.raise_for_status()
+            data = res.json()
+        return ToolResult(title=f"✅ {table_name} 检索成功", output=json.dumps(data, ensure_ascii=False, indent=2))
+    except httpx.HTTPStatusError as e:
+        return ToolResult(title=f"❌ {table_name} 检索失败", output=f"HTTP Error: {e.response.text}", error=str(e))
+    except Exception as e:
+        return ToolResult(title=f"❌ {table_name} 检索失败", output=str(e), error=str(e))

+ 2 - 2
agent/tools/builtin/librarian.py

@@ -17,7 +17,7 @@ from agent.tools import tool, ToolResult, ToolContext
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
-KNOWHUB_API = os.getenv("KNOWHUB_API", "http://localhost:8000").rstrip("/")
+KNOWHUB_API = os.getenv("KNOWHUB_API", "http://localhost:9999").rstrip("/")
 
 
 
 
 @tool(
 @tool(
@@ -46,7 +46,7 @@ async def ask_knowledge(
         整合回答 + source_ids + 各 source 摘要
         整合回答 + source_ids + 各 source 摘要
     """
     """
     try:
     try:
-        async with httpx.AsyncClient(timeout=60.0) as client:
+        async with httpx.AsyncClient(timeout=300.0) as client:
             response = await client.post(
             response = await client.post(
                 f"{KNOWHUB_API}/api/knowledge/ask",
                 f"{KNOWHUB_API}/api/knowledge/ask",
                 json={
                 json={

Разлика између датотеке није приказан због своје велике величине
+ 4 - 0
cache/knowledge_pending/knowledge-2026-04-09-x-portrait-workflows.json


+ 80 - 0
direct_insert.py

@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+"""
+直接插入知识到数据库的脚本
+"""
+import sys
+import os
+import uuid
+import time
+import asyncio
+from datetime import datetime, timezone
+
+sys.path.insert(0, '/root/Agent')
+
+# 设置环境变量
+os.environ['KNOWHUB_DB'] = 'gp-t4n72471pkmt4b9q7o-master.gpdbmaster.singapore.rds.aliyuncs.com'
+os.environ['KNOWHUB_PORT'] = '5432'
+os.environ['KNOWHUB_USER'] = 'aiddit_aigc'
+os.environ['KNOWHUB_PASSWORD'] = '%a&&yqNxg^V1$toJ*WOa^-b^X=QJ'
+os.environ['KNOWHUB_DB_NAME'] = 'knowhub'
+
+from knowhub.knowhub_db.pg_store import PostgreSQLStore
+from knowhub.embeddings import get_embedding
+
+# 测试知识数据
+knowledge_data = {
+    'task': '接口测试验证条目',
+    'content': '这是一条用于验证 upload 接口是否可用的测试知识,可以删除。',
+    'types': ['experience'],
+    'tags': {'source': 'api_test'},
+    'score': 1
+}
+
+async def main():
+    print('正在连接数据库...')
+    store = PostgreSQLStore()
+
+    # 生成 ID
+    now = datetime.now(timezone.utc)
+    knowledge_id = f'knowledge-{now.strftime("%Y%m%d")}-{uuid.uuid4().hex[:8]}'
+    message_id = f'msg-{uuid.uuid4().hex[:12]}'
+
+    # 生成 embedding
+    print('正在生成 embedding...')
+    task_embedding = await get_embedding(knowledge_data['task'])
+    content_embedding = await get_embedding(knowledge_data['content'])
+
+    # 构建知识记录
+    knowledge = {
+        'id': knowledge_id,
+        'message_id': message_id,
+        'task': knowledge_data['task'],
+        'content': knowledge_data['content'],
+        'types': knowledge_data['types'],
+        'tags': knowledge_data.get('tags', {}),
+        'tag_keys': list(knowledge_data.get('tags', {}).keys()),
+        'scopes': ['org:cybertogether'],
+        'owner': 'system',
+        'source': {'category': 'execution'},
+        'eval': {'score': knowledge_data.get('score', 3)},
+        'task_embedding': task_embedding,
+        'content_embedding': content_embedding,
+        'created_at': int(time.time()),
+        'updated_at': int(time.time()),
+        'status': 'approved',
+    }
+
+    print(f'正在插入知识条目...')
+    store.insert(knowledge)
+    store.close()
+
+    print(f'✅ 成功插入知识条目!')
+    print(f'  ID: {knowledge_id}')
+    print(f'  Task: {knowledge_data["task"]}')
+    print(f'  Types: {knowledge_data["types"]}')
+    
+    return knowledge_id
+
+if __name__ == '__main__':
+    knowledge_id = asyncio.run(main())
+    print(f'\n最终知识 ID: {knowledge_id}')

+ 13 - 0
im-server/__main__.py

@@ -0,0 +1,13 @@
+import uvicorn
+import os
+import sys
+
+# 确保在导入 main 时能够找到同级模块
+current_dir = os.path.dirname(os.path.abspath(__file__))
+if current_dir not in sys.path:
+    sys.path.insert(0, current_dir)
+
+from main import app
+
+if __name__ == "__main__":
+    uvicorn.run(app, host="0.0.0.0", port=8105)

+ 132 - 0
insert_ai_workflow_capabilities.py

@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+"""
+插入 AI 创作工作流的 16 个通用能力模块知识条目
+"""
+import os
+import sys
+import json
+import uuid
+from datetime import datetime, timezone
+from pathlib import Path
+
+# 添加项目路径
+sys.path.insert(0, '/root/Agent')
+
+os.environ['OPEN_ROUTER_API_KEY'] = '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'
+os.environ['QWEN_API_KEY'] = 'sk-9453c827b9e14108b53d2b30ef7c75fe'
+os.environ['KNOWHUB_DB'] = 'gp-t4n72471pkmt4b9q7o-master.gpdbmaster.singapore.rds.aliyuncs.com'
+os.environ['KNOWHUB_PORT'] = '5432'
+os.environ['KNOWHUB_USER'] = 'aiddit_aigc'
+os.environ['KNOWHUB_PASSWORD'] = '%a&&yqNxg^V1$toJ*WOa^-b^X=QJ'
+os.environ['KNOWHUB_DB_NAME'] = 'knowhub'
+
+from knowhub.knowhub_db.pg_store import PostgreSQLStore
+from knowhub.embeddings import get_embedding
+
+# 知识数据 - AI 创作工作流的 16 个通用能力模块
+knowledge_data = {
+    "task": "AI 创作工作流的 16 个通用能力模块",
+    "content": """## 能力模块清单
+
+### 素材准备类
+1. **准备参考素材**:收集或生成用于后续创作的参考图像、纹理、角色基础等素材
+2. **生成参考图表**:生成角色参考表、多视角图、多表情图等参考资料
+
+### 角色定义类
+3. **定义角色特征**:设定人物面部特征、表情状态、身份一致性等角色属性
+
+### 场景构建类
+4. **构建场景环境**:创建或合成场景背景、虚拟环境、空间布局
+
+### 视觉控制类
+5. **配置光影效果**:设置光源方向、灯光类型、明暗对比等光照条件
+6. **控制姿态构图**:控制人物姿态、画面构图、视角、摄影参数
+7. **应用风格控制**:应用风格迁移、保持视觉一致性、注入特定美学风格
+8. **处理纹理效果**:添加纹理、颗粒、质感、色调等视觉效果
+9. **创建纹理布局**:创建四方连续、渐变网格、矢量图形等纹理布局
+
+### 生成执行类
+10. **执行内容生成**:执行核心的图像、视频、渲染生成操作
+11. **配置控制技术**:配置 ControlNet、深度图、姿态图等控制条件
+12. **配置工作流**:配置工具节点、构建流程、设置参数
+
+### 后期处理类
+13. **修复细节瑕疵**:对局部区域进行重绘、修复瑕疵、优化细节
+14. **增强图像质量**:提升图像分辨率、质量增强、最终精修
+15. **后期合成处理**:抠图、选区创建、图像合成、图层处理
+16. **视频后期处理**:视频剪辑、颜色分级、特效增强
+
+## 跨品类复用性
+以上模块均从 5 个不同品类的工作流(角色创作、人像精修、肖像摄影、光影控制、纹理设计)中抽象得出,可跨品类复用。""",
+    "types": ["strategy"],
+    "tags": {
+        "domain": "AI 创作",
+        "category": "能力模块",
+        "source": "工作流分析 pipeline"
+    },
+    "score": 5,
+    "source": {"category": "research"}
+}
+
+print("正在连接数据库...")
+store = PostgreSQLStore()
+
+# 生成 ID
+now = datetime.now(timezone.utc)
+knowledge_id = f"knowledge-{now.strftime('%Y%m%d')}-{uuid.uuid4().hex[:8]}"
+message_id = f"msg-{uuid.uuid4().hex[:12]}"
+
+# 生成 embedding
+print("正在生成 embedding...")
+task_embedding = get_embedding(knowledge_data["task"])
+content_embedding = get_embedding(knowledge_data["content"])
+
+# 构建知识记录
+knowledge = {
+    "id": knowledge_id,
+    "message_id": message_id,
+    "task": knowledge_data["task"],
+    "content": knowledge_data["content"],
+    "types": knowledge_data["types"],
+    "tags": knowledge_data["tags"],
+    "tag_keys": list(knowledge_data["tags"].keys()),
+    "scopes": [],
+    "owner": "librarian",
+    "source": knowledge_data["source"],
+    "eval": {"score": knowledge_data["score"]},
+    "created_at": now.isoformat(),
+    "updated_at": now.isoformat(),
+    "status": "approved",
+    "task_embedding": task_embedding,
+    "content_embedding": content_embedding,
+    "requirement_ids": [],
+    "capability_ids": [],
+    "tool_ids": [],
+    "resource_ids": [],
+}
+
+print(f"正在插入知识:{knowledge_id}")
+print(f"  Task: {knowledge['task']}")
+print(f"  Types: {knowledge['types']}")
+print(f"  Tags: {knowledge['tags']}")
+print(f"  Content 长度:{len(knowledge['content'])} 字符")
+
+store.insert(knowledge)
+
+print(f"\n✓ 知识已成功入库!")
+print(f"  ID: {knowledge_id}")
+print(f"  Message ID: {message_id}")
+
+# 验证插入
+retrieved = store.get_by_id(knowledge_id)
+if retrieved:
+    print(f"\n✓ 验证成功:已检索到刚插入的知识")
+    print(f"  Task: {retrieved['task']}")
+    print(f"  Content 长度:{len(retrieved['content'])} 字符")
+    print(f"  Types: {retrieved['types']}")
+    print(f"  Tags: {retrieved['tags']}")
+else:
+    print(f"\n✗ 验证失败:无法检索到刚插入的知识")
+
+store.close()
+print("\n数据库连接已关闭")

+ 73 - 0
insert_knowledge.py

@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+"""
+插入知识条目到 PostgreSQL 数据库
+"""
+
+import asyncio
+import uuid
+import time
+import sys
+import os
+
+# 添加项目路径
+sys.path.insert(0, '/root/Agent')
+
+from knowhub.knowhub_db.pg_store import PostgreSQLStore
+from knowhub.embeddings import get_embedding
+
+
+async def main():
+    # 知识数据
+    task = "人物近景肖像"
+    content = "生成人物近景半身或胸部以上画面、突出面部表情和情绪、背景虚化的核心工序要素包括:1.近景/半身构图 2.面部表情细节 3.背景虚化效果 4.皮肤质感真实 5.眼神互动感"
+    types = ["strategy"]
+    tags = {"domain": "AI 生图", "source": "多渠调研汇总", "task": "人物近景肖像"}
+    score = 5
+    source = {"category": "research"}
+    
+    # 生成唯一 ID
+    knowledge_id = str(uuid.uuid4())
+    message_id = str(uuid.uuid4())
+    current_time = int(time.time())
+    
+    # 生成 embeddings
+    print(f"正在生成 task embedding...")
+    task_embedding = await get_embedding(task)
+    print(f"正在生成 content embedding...")
+    content_embedding = await get_embedding(content)
+    
+    # 构建知识对象
+    knowledge = {
+        'id': knowledge_id,
+        'message_id': message_id,
+        'task': task,
+        'content': content,
+        'types': types,
+        'tags': tags,
+        'tag_keys': list(tags.keys()),
+        'scopes': ['org:cybertogether'],  # 默认可见范围
+        'owner': 'system',  # 默认所有者
+        'source': source,
+        'eval': {'score': score},
+        'task_embedding': task_embedding,
+        'content_embedding': content_embedding,
+        'created_at': current_time,
+        'updated_at': current_time,
+        'status': 'approved',
+    }
+    
+    # 插入数据库
+    print(f"正在插入知识条目到 PostgreSQL...")
+    store = PostgreSQLStore()
+    store.insert(knowledge)
+    store.close()
+    
+    print(f"成功插入知识条目!")
+    print(f"  ID: {knowledge_id}")
+    print(f"  Task: {task}")
+    print(f"  Types: {types}")
+    print(f"  Score: {score}")
+
+
+if __name__ == '__main__':
+    asyncio.run(main())

+ 91 - 0
insert_knowledge_direct.py

@@ -0,0 +1,91 @@
+import os
+import sys
+import json
+import uuid
+from datetime import datetime, timezone
+from pathlib import Path
+
+# 添加项目路径
+sys.path.insert(0, '/root/Agent')
+
+os.environ['OPEN_ROUTER_API_KEY'] = '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'
+os.environ['QWEN_API_KEY'] = 'sk-9453c827b9e14108b53d2b30ef7c75fe'
+os.environ['KNOWHUB_DB'] = 'gp-t4n72471pkmt4b9q7o-master.gpdbmaster.singapore.rds.aliyuncs.com'
+os.environ['KNOWHUB_PORT'] = '5432'
+os.environ['KNOWHUB_USER'] = 'aiddit_aigc'
+os.environ['KNOWHUB_PASSWORD'] = '%a&&yqNxg^V1$toJ*WOa^-b^X=QJ'
+os.environ['KNOWHUB_DB_NAME'] = 'knowhub'
+
+from knowhub.knowhub_db.pg_store import PostgreSQLStore
+from knowhub.embeddings import get_embedding
+
+# 知识数据
+knowledge_data = {
+    "task": "人物近景肖像",
+    "content": "生成人物近景半身或胸部以上画面、突出面部表情和情绪、背景虚化的核心工序要素包括:\n\n1. **近景/半身构图**:使用 tight medium close-up、extreme close-up、中近景半身等提示词;推荐 85mm 镜头视角;构图比例 2:3 或 9:16(竖版)、3:4(职业照)\n\n2. **面部表情细节**:使用锁脸技术(nano-banana 锁脸功能)、微表情描述(slightly parted lips、calm introspective expression)、多表情生成(3×3 网格生成 9 种表情);关键提示词:direct steady eye contact、subtle catchlights in eyes\n\n3. **背景虚化效果**:推荐参数 85mm 镜头 + f/1.4-f/1.8 光圈;提示词:shallow depth of field、extremely shallow depth of field、soft bokeh blur、blurring out the neck, ear, and background completely;后期工具:Photoshop Lens Blur、DxO Photolab 9 Bokeh\n\n4. **皮肤质感真实**:避免塑料感的关键提示词:realistic skin texture with visible pores and natural imperfections、no plastic smoothing、dewy smooth texture;修复工具:Enhancor.ai Skin Realism\n\n5. **眼神互动感**:direct eye contact、catchlights in eyes、gentle and engaging eyes、looking directly at the camera",
+    "types": ["strategy"],
+    "tags": {
+        "domain": "AI 生图",
+        "source": "多渠调研汇总",
+        "task": "人物近景肖像"
+    },
+    "score": 5,
+    "source": {"category": "research"}
+}
+
+print("正在连接数据库...")
+store = PostgreSQLStore()
+
+# 生成 ID
+now = datetime.now(timezone.utc)
+knowledge_id = f"knowledge-{now.strftime('%Y%m%d')}-{uuid.uuid4().hex[:8]}"
+message_id = f"msg-{uuid.uuid4().hex[:12]}"
+
+# 生成 embedding
+print("正在生成 embedding...")
+task_embedding = get_embedding(knowledge_data["task"])
+content_embedding = get_embedding(knowledge_data["content"])
+
+# 构建知识记录
+knowledge = {
+    "id": knowledge_id,
+    "message_id": message_id,
+    "task": knowledge_data["task"],
+    "content": knowledge_data["content"],
+    "types": knowledge_data["types"],
+    "tags": knowledge_data["tags"],
+    "tag_keys": list(knowledge_data["tags"].keys()),
+    "scopes": [],
+    "owner": "librarian",
+    "source": knowledge_data["source"],
+    "eval": {"score": knowledge_data["score"]},
+    "created_at": now.isoformat(),
+    "updated_at": now.isoformat(),
+    "status": "approved",
+    "task_embedding": task_embedding,
+    "content_embedding": content_embedding,
+    "requirement_ids": [],
+    "capability_ids": [],
+    "tool_ids": [],
+    "resource_ids": [],
+}
+
+print(f"正在插入知识:{knowledge_id}")
+print(f"  Task: {knowledge['task']}")
+print(f"  Types: {knowledge['types']}")
+print(f"  Tags: {knowledge['tags']}")
+
+store.insert(knowledge)
+
+print(f"\n✓ 知识已成功入库!")
+print(f"  ID: {knowledge_id}")
+print(f"  Message ID: {message_id}")
+
+# 验证插入
+retrieved = store.get_by_id(knowledge_id)
+if retrieved:
+    print(f"\n✓ 验证成功:已检索到刚插入的知识")
+    print(f"  Task: {retrieved['task']}")
+    print(f"  Content 长度:{len(retrieved['content'])} 字符")
+else:
+    print(f"\n✗ 验证失败:无法检索到刚插入的知识")

+ 1 - 0
knowhub/agents/librarian.py

@@ -66,6 +66,7 @@ def get_librarian_config(enable_db_commit: bool = ENABLE_DATABASE_COMMIT) -> Run
             enable_injection=False,
             enable_injection=False,
         ),
         ),
         tools=tools,
         tools=tools,
+        exclude_tools=["ask_knowledge", "upload_knowledge", "bash_command", "grep_content", "glob_files"],
     )
     )
 
 
 
 

+ 5 - 0
knowhub/agents/librarian_agent.prompt

@@ -44,5 +44,10 @@ Knowledge 按 types 分类:
 
 
 所有关联通过关联表存储,数据库保证引用完整性。查到任意一个实体都可以顺藤摸瓜找到关联的其他实体。
 所有关联通过关联表存储,数据库保证引用完整性。查到任意一个实体都可以顺藤摸瓜找到关联的其他实体。
 
 
+## 工具使用规范与检索策略
+
+1. **精准查询优于全文搜索**:当你需要查询跨表关联关系时(例如:“寻找某个特定 Capability ID 被哪些 Requirement 关联了” 或者 “查某个 Tool 有没有被某个 Capability 包含”),**强烈推荐且务必优先使用 `relation_search` 工具**直接查询关系表(例如 `requirement_capability`, `capability_tool`, `tool_knowledge` 等)。
+2. 使用 `relation_search` 时,在 `filters` 中传入已知的 `_id` 即可迅速获得所有匹配的关联链路,进而拿到目标实体的 ID 再去定向获取详情,这比漫无目的地做大文本向量搜索 (`search` 结尾工具) 效率极速且精准得多!
+
 $user$
 $user$
 
 

+ 444 - 0
knowhub/docs/knowledge-management.md

@@ -0,0 +1,444 @@
+# KnowHub API 参考
+
+KnowHub 是一个 FastAPI 服务,提供知识图谱的存储、检索和管理能力。
+基础地址由环境变量 `KNOWHUB_API` 控制,默认 `http://localhost:8000`。
+
+FastAPI 自动生成的交互式文档:`{KNOWHUB_API}/docs`
+
+---
+
+## 数据模型
+
+### Knowledge(知识)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | string | 格式 `knowledge-{date}-{hash}`,自动生成 |
+| `task` | string | 任务描述(在什么情景下要完成什么目标) |
+| `content` | string | 核心内容 |
+| `types` | string[] | 类型标签:`tool` / `strategy` / `case` / `experience` |
+| `tags` | object | 业务标签,如 `{"project": "foo", "domain": "ai"}` |
+| `scopes` | string[] | 可见范围,默认 `["org:cybertogether"]` |
+| `owner` | string | 所有者,默认 `agent:{agent_id}` |
+| `source` | object | `{name, category, urls, agent_id, submitted_by, timestamp}` |
+| `eval` | object | `{score(1-5), helpful, harmful, confidence}` |
+| `capability_ids` | string[] | 关联的能力 ID |
+| `tool_ids` | string[] | 关联的工具 ID |
+| `resource_ids` | string[] | 关联的资源 ID |
+| `status` | string | `pending` / `processing` / `dedup_passed` / `analyzing` / `approved` / `checked` / `rejected` |
+
+### Tool(工具)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | string | 格式 `tools/{category}/{name}`,调用方指定 |
+| `name` | string | 工具名称 |
+| `version` | string | 版本号(可选) |
+| `introduction` | string | 简介 |
+| `tutorial` | string | 使用教程 |
+| `input` | object/string | 输入格式描述 |
+| `output` | object/string | 输出格式描述 |
+| `status` | string | 默认 `未接入` |
+| `capability_ids` | string[] | 关联的能力 ID |
+| `knowledge_ids` | string[] | 关联的知识 ID |
+| `provider_ids` | string[] | 关联的提供方 ID |
+
+### Capability(原子能力)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | string | 格式 `CAP-XXX`,调用方指定 |
+| `name` | string | 能力名称 |
+| `criterion` | string | 判断标准(何时认为该能力被满足) |
+| `description` | string | 详细描述 |
+| `requirement_ids` | string[] | 关联的需求 ID |
+| `tool_ids` | string[] | 实现该能力的工具 ID |
+| `implements` | object | `{tool_id: description}` 工具实现描述 |
+| `knowledge_ids` | string[] | 关联的知识 ID |
+
+### Requirement(需求)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | string | 格式 `REQ_XXX`,调用方指定 |
+| `description` | string | 需求描述 |
+| `capability_ids` | string[] | 满足该需求的能力 ID |
+| `knowledge_ids` | string[] | 关联的知识 ID |
+| `source_nodes` | object[] | 分类树节点 `[{id, score}]` |
+| `status` | string | 默认 `未满足` |
+| `match_result` | string | 匹配结果说明 |
+
+### Resource(资源)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | string | 路径格式,如 `docs/foo/bar` |
+| `title` | string | 标题 |
+| `body` | string | 正文内容 |
+| `secure_body` | string | 加密内容(AES256-GCM) |
+| `content_type` | string | 默认 `text` |
+| `metadata` | object | 自定义元数据 |
+
+---
+
+## Knowledge API
+
+### 智能查询(Librarian Agent)
+
+#### `POST /api/knowledge/ask`
+
+同步阻塞。触发 Librarian Agent 跨表检索 + LLM 整合,返回带引用的回答。
+同一 `trace_id` 的多次调用复用同一个 Librarian trace,积累上下文。
+
+```json
+// 请求
+{
+  "query": "有没有工具能做角色一致性生成?",
+  "trace_id": "trace-abc123"
+}
+
+// 响应
+{
+  "response": "根据知识库,ControlNet 可以...",
+  "source_ids": ["knowledge-20260101-abcd"],
+  "sources": []
+}
+```
+
+#### `POST /api/knowledge/upload` → 202
+
+异步。校验后立即返回,后台 Librarian Agent 处理去重、关联、写草稿池。
+
+```json
+// 请求
+{
+  "data": {
+    "knowledge": [...],
+    "tools": [...],
+    "resources": [...]
+  },
+  "trace_id": "trace-abc123",
+  "finalize": false
+}
+
+// 响应 202
+{
+  "message": "已接收 知识: 2 个,Librarian Agent 后台处理中",
+  "buffer_file": ".cache/.knowledge/buffer/upload_20260101_120000.json"
+}
+```
+
+#### `GET /api/knowledge/upload/pending`
+
+列出所有未处理或失败的 upload 任务。
+
+#### `POST /api/knowledge/upload/retry`
+
+重跑所有 `status=failed` 的 upload 任务。
+
+---
+
+### 向量检索
+
+#### `GET /api/knowledge/search`
+
+向量召回 + LLM 精排。
+
+| 参数 | 类型 | 默认 | 说明 |
+|------|------|------|------|
+| `q` | string | 必填 | 查询文本 |
+| `top_k` | int | 5 | 返回条数(1-20) |
+| `min_score` | int | 3 | 最低评分过滤(1-5) |
+| `types` | string | - | 逗号分隔,如 `tool,case` |
+| `owner` | string | - | 逗号分隔,过滤 owner |
+
+```json
+// 响应
+{
+  "results": [{"id": "...", "task": "...", "content": "...", "score": 0.92, ...}],
+  "count": 3
+}
+```
+
+---
+
+### CRUD
+
+#### `POST /api/knowledge` → 201
+
+创建知识,进入 `pending` 状态,自动触发去重流水线。
+
+```json
+{
+  "task": "使用 ControlNet 进行姿态控制",
+  "content": "...",
+  "types": ["tool"],
+  "tags": {"project": "image_gen"},
+  "source": {"agent_id": "research_agent", "category": "research"},
+  "eval": {"score": 4}
+}
+// 响应: {"status": "pending", "knowledge_id": "knowledge-20260101-abcd"}
+```
+
+#### `GET /api/knowledge`
+
+列出知识,支持分页和过滤。
+
+| 参数 | 说明 |
+|------|------|
+| `page` / `page_size` | 分页,默认 1/20 |
+| `types` | 逗号分隔类型过滤 |
+| `scopes` | 逗号分隔范围过滤 |
+| `owner` | owner 过滤 |
+| `tags` | JSON 字符串,如 `{"project":"foo"}` |
+| `status` | 状态过滤 |
+
+#### `GET /api/knowledge/{knowledge_id}`
+
+获取单条知识详情。
+
+#### `GET /api/knowledge/status/{knowledge_id}`
+
+查询知识处理状态(`pending` / `approved` 等)。
+
+#### `PUT /api/knowledge/{knowledge_id}`
+
+更新知识评估,支持知识进化(LLM 重写 content)。
+
+```json
+{
+  "update_score": 5,
+  "add_helpful_case": {"context": "...", "result": "success"},
+  "add_harmful_case": null,
+  "evolve_feedback": "内容需要补充 batch 处理的情况"
+}
+```
+
+#### `PATCH /api/knowledge/{knowledge_id}`
+
+直接编辑字段(task、content、types、tags、scopes、owner、capability_ids、tool_ids)。
+
+#### `DELETE /api/knowledge/{knowledge_id}`
+
+删除单条知识(级联清理关联表)。
+
+#### `POST /api/knowledge/batch_delete`
+
+批量删除。请求体为 `["knowledge-xxx", "knowledge-yyy"]`。
+
+---
+
+### 审核与验证
+
+#### `POST /api/knowledge/{knowledge_id}/verify`
+
+```json
+// approve:approved ↔ checked 切换;reject:→ rejected
+{"action": "approve", "verified_by": "howard"}
+```
+
+#### `POST /api/knowledge/batch_verify`
+
+```json
+{"knowledge_ids": ["knowledge-xxx"], "action": "approve", "verified_by": "howard"}
+```
+
+---
+
+### 运维工具
+
+#### `GET /api/knowledge/pending`
+
+查询处理队列(`pending` / `processing` / `dedup_passed` / `analyzing`)。
+
+#### `POST /api/knowledge/process`
+
+手动触发去重处理。`force=true` 时先回滚所有超时锁。
+
+#### `GET /api/knowledge/meta/tags`
+
+获取所有已使用的 tag keys。
+
+#### `POST /api/knowledge/slim`
+
+知识库瘦身:LLM 分析全库,合并语义相似条目。
+可选参数 `model`,默认 `google/gemini-2.5-flash-lite`。
+
+#### `POST /api/knowledge/batch_update`
+
+批量反馈知识有效性。
+
+#### `POST /api/extract`
+
+从消息历史中 LLM 提取知识并入库。
+
+```json
+{
+  "messages": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}],
+  "agent_id": "research_agent",
+  "submitted_by": "howard@example.com",
+  "session_key": ""
+}
+```
+
+---
+
+## Tool API
+
+#### `POST /api/tool` → 201
+
+创建或更新工具(id 相同则覆盖,同时全量替换关联表)。
+
+```json
+{
+  "id": "tools/image/controlnet",
+  "name": "ControlNet",
+  "introduction": "基于条件控制的图像生成工具",
+  "tutorial": "...",
+  "status": "已接入",
+  "capability_ids": ["CAP-001"],
+  "knowledge_ids": ["knowledge-20260101-abcd"]
+}
+```
+
+#### `GET /api/tool`
+
+列出工具。参数:`status`、`limit`(默认100)、`offset`。
+
+#### `GET /api/tool/search?q=...&top_k=5`
+
+向量检索工具。
+
+#### `GET /api/tool/{tool_id}`
+
+获取单个工具详情(含关联的 capability_ids、knowledge_ids)。
+
+#### `PATCH /api/tool/{tool_id}`
+
+部分更新工具字段。
+
+#### `DELETE /api/tool/{tool_id}`
+
+删除工具(级联清理关联表)。
+
+---
+
+## Capability API
+
+#### `POST /api/capability` → 201
+
+创建或更新原子能力。
+
+```json
+{
+  "id": "CAP-001",
+  "name": "人物姿态控制生成",
+  "criterion": "能根据骨骼图生成指定姿态的人物图像",
+  "description": "...",
+  "tool_ids": ["tools/image/controlnet"],
+  "implements": {"tools/image/controlnet": "使用 openpose 模型控制姿态"}
+}
+```
+
+#### `GET /api/capability`
+
+列出所有能力。参数:`limit`、`offset`。
+
+#### `GET /api/capability/search?q=...&top_k=5`
+
+向量检索能力。
+
+#### `GET /api/capability/{cap_id}`
+
+获取单个能力详情。
+
+#### `PATCH /api/capability/{cap_id}`
+
+部分更新能力字段。
+
+#### `DELETE /api/capability/{cap_id}`
+
+删除能力(级联清理关联表)。
+
+---
+
+## Requirement API
+
+#### `POST /api/requirement` → 201
+
+创建或更新需求。
+
+```json
+{
+  "id": "REQ_001",
+  "description": "需要能生成角色一致的多视角图像",
+  "capability_ids": ["CAP-001"],
+  "source_nodes": [{"id": "image_generation", "score": 0.9}],
+  "status": "已满足"
+}
+```
+
+#### `GET /api/requirement`
+
+列出需求。参数:`status`、`limit`、`offset`。
+
+#### `GET /api/requirement/search?q=...&top_k=5`
+
+向量检索需求。
+
+#### `GET /api/requirement/{req_id}`
+
+获取单个需求详情。
+
+#### `PATCH /api/requirement/{req_id}`
+
+部分更新需求字段。
+
+#### `DELETE /api/requirement/{req_id}`
+
+删除需求(级联清理关联表)。
+
+---
+
+## Resource API
+
+Resource 存储原始资料文档,通过 `knowledge_resource` 关联表与 Knowledge 关联。
+
+#### `POST /api/resource` → 201
+
+创建或更新资源。
+
+#### `GET /api/resource`
+
+列出资源(树形结构)。
+
+#### `GET /api/resource/{resource_id}`
+
+获取单个资源(含 toc、children、prev/next 导航)。
+
+#### `PATCH /api/resource/{resource_id}`
+
+部分更新资源字段。
+
+#### `DELETE /api/resource/{resource_id}`
+
+删除资源。
+
+---
+
+## 关联表说明
+
+所有实体间的关系通过 junction table 维护,写入时**全量替换**(DELETE + INSERT):
+
+| 关联表 | 连接 |
+|--------|------|
+| `capability_tool` | Capability ↔ Tool |
+| `capability_knowledge` | Capability ↔ Knowledge |
+| `requirement_capability` | Requirement ↔ Capability |
+| `requirement_knowledge` | Requirement ↔ Knowledge |
+| `tool_knowledge` | Tool ↔ Knowledge |
+| `tool_provider` | Tool ↔ Provider |
+| `knowledge_resource` | Knowledge ↔ Resource |
+| `knowledge_relation` | Knowledge → Knowledge(supplement/duplicate) |
+
+查询任意实体时,关联 ID 通过子查询自动聚合返回(`capability_ids`、`tool_ids` 等字段)。

+ 44 - 7
knowhub/frontend/src/App.tsx

@@ -1,4 +1,7 @@
+import { useState } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
 import { MainLayout } from './layouts/MainLayout';
 import { MainLayout } from './layouts/MainLayout';
+import type { TabId } from './components/layout/Navbar';
 import { Dashboard } from './pages/Dashboard';
 import { Dashboard } from './pages/Dashboard';
 import { Relations } from './pages/Relations';
 import { Relations } from './pages/Relations';
 import { Requirements } from './pages/Requirements';
 import { Requirements } from './pages/Requirements';
@@ -6,15 +9,49 @@ import { Capabilities } from './pages/Capabilities';
 import { Tools } from './pages/Tools';
 import { Tools } from './pages/Tools';
 import { Knowledge } from './pages/Knowledge';
 import { Knowledge } from './pages/Knowledge';
 
 
+const PATH_TO_TAB: Record<string, TabId> = {
+  '/': 'dashboard',
+  '/dashboard': 'dashboard',
+  '/relations': 'relations',
+  '/requirements': 'requirements',
+  '/capabilities': 'capabilities',
+  '/tools': 'tools',
+  '/knowledge': 'knowledge',
+};
+
+const TAB_TO_PATH: Record<TabId, string> = {
+  dashboard: '/',
+  relations: '/relations',
+  requirements: '/requirements',
+  capabilities: '/capabilities',
+  tools: '/tools',
+  knowledge: '/knowledge',
+};
+
 function App() {
 function App() {
+  const navigate = useNavigate();
+  const location = useLocation();
+  const [pendingDashboardNode, setPendingDashboardNode] = useState<string | null>(null);
+
+  const activeTab: TabId = PATH_TO_TAB[location.pathname] ?? 'dashboard';
+
+  const handleTabChange = (tab: TabId) => {
+    navigate(TAB_TO_PATH[tab]);
+  };
+
+  const navigateToDashboard = (nodeName: string) => {
+    setPendingDashboardNode(nodeName);
+    navigate('/');
+  };
+
   return (
   return (
-    <MainLayout>
-      {(activeTab) => {
-        switch (activeTab) {
+    <MainLayout activeTab={activeTab} onTabChange={handleTabChange}>
+      {(tab) => {
+        switch (tab) {
           case 'dashboard':
           case 'dashboard':
-            return <Dashboard />;
+            return <Dashboard pendingNode={pendingDashboardNode} onPendingConsumed={() => setPendingDashboardNode(null)} />;
           case 'relations':
           case 'relations':
-            return <Relations />;
+            return <Relations onNavigateToDashboard={navigateToDashboard} />;
           case 'requirements':
           case 'requirements':
             return <Requirements />;
             return <Requirements />;
           case 'capabilities':
           case 'capabilities':
@@ -24,11 +61,11 @@ function App() {
           case 'knowledge':
           case 'knowledge':
             return <Knowledge />;
             return <Knowledge />;
           default:
           default:
-            return <Dashboard />;
+            return <Dashboard pendingNode={null} onPendingConsumed={() => {}} />;
         }
         }
       }}
       }}
     </MainLayout>
     </MainLayout>
   );
   );
 }
 }
 
 
-export default App;
+export default App;

+ 23 - 51
knowhub/frontend/src/layouts/MainLayout.tsx

@@ -1,8 +1,10 @@
-import { useState, useEffect, useRef } from 'react';
+import { useEffect, useRef } from 'react';
 import { Navbar } from '../components/layout/Navbar';
 import { Navbar } from '../components/layout/Navbar';
 import type { TabId } from '../components/layout/Navbar';
 import type { TabId } from '../components/layout/Navbar';
 
 
 interface MainLayoutProps {
 interface MainLayoutProps {
+  activeTab: TabId;
+  onTabChange: (tab: TabId) => void;
   children: (activeTab: TabId) => React.ReactNode;
   children: (activeTab: TabId) => React.ReactNode;
 }
 }
 
 
@@ -10,9 +12,7 @@ interface MainLayoutProps {
 const TAB_ORDER: TabId[] = ['dashboard', 'relations', 'requirements', 'capabilities', 'tools', 'knowledge'];
 const TAB_ORDER: TabId[] = ['dashboard', 'relations', 'requirements', 'capabilities', 'tools', 'knowledge'];
 const MIN_SWITCH_INTERVAL = 1000;
 const MIN_SWITCH_INTERVAL = 1000;
 
 
-export function MainLayout({ children }: MainLayoutProps) {
-  const [activeTab, setActiveTab] = useState<TabId>('dashboard');
-
+export function MainLayout({ activeTab, onTabChange, children }: MainLayoutProps) {
   const lastSwitchTime = useRef(0);
   const lastSwitchTime = useRef(0);
   const accumX = useRef(0);
   const accumX = useRef(0);
   const touchStartX = useRef(0);
   const touchStartX = useRef(0);
@@ -22,17 +22,12 @@ export function MainLayout({ children }: MainLayoutProps) {
     const handleTabSwitch = (direction: 1 | -1) => {
     const handleTabSwitch = (direction: 1 | -1) => {
       const now = Date.now();
       const now = Date.now();
       if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) return;
       if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) return;
-
-      setActiveTab(prev => {
-        const currentIndex = TAB_ORDER.indexOf(prev);
-        const nextIndex = currentIndex + direction;
-
-        if (nextIndex >= 0 && nextIndex < TAB_ORDER.length) {
-          lastSwitchTime.current = now;
-          return TAB_ORDER[nextIndex];
-        }
-        return prev;
-      });
+      const currentIndex = TAB_ORDER.indexOf(activeTab);
+      const nextIndex = currentIndex + direction;
+      if (nextIndex >= 0 && nextIndex < TAB_ORDER.length) {
+        lastSwitchTime.current = now;
+        onTabChange(TAB_ORDER[nextIndex]);
+      }
     };
     };
 
 
     const isInsideHorizontallyScrollable = (targetNode: EventTarget | null) => {
     const isInsideHorizontallyScrollable = (targetNode: EventTarget | null) => {
@@ -40,9 +35,7 @@ export function MainLayout({ children }: MainLayoutProps) {
       while (target && target !== document.body) {
       while (target && target !== document.body) {
         if (target.scrollWidth > target.clientWidth) {
         if (target.scrollWidth > target.clientWidth) {
           const style = window.getComputedStyle(target);
           const style = window.getComputedStyle(target);
-          if (style.overflowX === 'auto' || style.overflowX === 'scroll' || style.overflowX === 'overlay') {
-            return true;
-          }
+          if (style.overflowX === 'auto' || style.overflowX === 'scroll' || style.overflowX === 'overlay') return true;
         }
         }
         target = target.parentElement;
         target = target.parentElement;
       }
       }
@@ -53,25 +46,13 @@ export function MainLayout({ children }: MainLayoutProps) {
       if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) return;
       if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) return;
       if (isInsideHorizontallyScrollable(e.target)) return;
       if (isInsideHorizontallyScrollable(e.target)) return;
       if (e.cancelable) e.preventDefault();
       if (e.cancelable) e.preventDefault();
-
       const now = Date.now();
       const now = Date.now();
-      if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) {
-        accumX.current = 0;
-        return;
-      }
-
+      if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) { accumX.current = 0; return; }
       accumX.current += e.deltaX;
       accumX.current += e.deltaX;
-
       clearTimeout(wheelTimeout.current);
       clearTimeout(wheelTimeout.current);
       wheelTimeout.current = setTimeout(() => { accumX.current = 0; }, 150);
       wheelTimeout.current = setTimeout(() => { accumX.current = 0; }, 150);
-
-      if (accumX.current > 120) {
-        handleTabSwitch(1);
-        accumX.current = 0;
-      } else if (accumX.current < -120) {
-        handleTabSwitch(-1);
-        accumX.current = 0;
-      }
+      if (accumX.current > 120) { handleTabSwitch(1); accumX.current = 0; }
+      else if (accumX.current < -120) { handleTabSwitch(-1); accumX.current = 0; }
     };
     };
 
 
     const handleTouchStart = (e: TouchEvent) => {
     const handleTouchStart = (e: TouchEvent) => {
@@ -81,10 +62,8 @@ export function MainLayout({ children }: MainLayoutProps) {
 
 
     const handleTouchEnd = (e: TouchEvent) => {
     const handleTouchEnd = (e: TouchEvent) => {
       if (isInsideHorizontallyScrollable(e.target)) return;
       if (isInsideHorizontallyScrollable(e.target)) return;
-
       const now = Date.now();
       const now = Date.now();
       if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) return;
       if (now - lastSwitchTime.current < MIN_SWITCH_INTERVAL) return;
-
       const diffX = touchStartX.current - e.changedTouches[0].clientX;
       const diffX = touchStartX.current - e.changedTouches[0].clientX;
       if (diffX > 60) handleTabSwitch(1);
       if (diffX > 60) handleTabSwitch(1);
       else if (diffX < -60) handleTabSwitch(-1);
       else if (diffX < -60) handleTabSwitch(-1);
@@ -93,42 +72,35 @@ export function MainLayout({ children }: MainLayoutProps) {
     window.addEventListener('wheel', handleWheel, { passive: false });
     window.addEventListener('wheel', handleWheel, { passive: false });
     window.addEventListener('touchstart', handleTouchStart, { passive: true });
     window.addEventListener('touchstart', handleTouchStart, { passive: true });
     window.addEventListener('touchend', handleTouchEnd, { passive: true });
     window.addEventListener('touchend', handleTouchEnd, { passive: true });
-
     return () => {
     return () => {
       window.removeEventListener('wheel', handleWheel);
       window.removeEventListener('wheel', handleWheel);
       window.removeEventListener('touchstart', handleTouchStart);
       window.removeEventListener('touchstart', handleTouchStart);
       window.removeEventListener('touchend', handleTouchEnd);
       window.removeEventListener('touchend', handleTouchEnd);
       clearTimeout(wheelTimeout.current);
       clearTimeout(wheelTimeout.current);
     };
     };
-  }, []);
+  }, [activeTab, onTabChange]);
 
 
   const currentIndex = TAB_ORDER.indexOf(activeTab);
   const currentIndex = TAB_ORDER.indexOf(activeTab);
   const totalTabs = TAB_ORDER.length;
   const totalTabs = TAB_ORDER.length;
 
 
   return (
   return (
     <div className="min-h-screen bg-slate-50 flex flex-col overflow-x-hidden" style={{ overscrollBehaviorX: 'none' }}>
     <div className="min-h-screen bg-slate-50 flex flex-col overflow-x-hidden" style={{ overscrollBehaviorX: 'none' }}>
-      <Navbar activeTab={activeTab} onTabChange={setActiveTab} />
-
-      <main className="flex-1 w-full overflow-x-hidden relative">
+      <Navbar activeTab={activeTab} onTabChange={onTabChange} />
+      <main className="w-full overflow-hidden relative" style={{ height: 'calc(100vh - 60px)' }}>
         <div
         <div
           className="flex h-full will-change-transform"
           className="flex h-full will-change-transform"
           style={{
           style={{
-            // 轨道总宽度由标签页数量动态决定,6个页面就是 600%
             width: `${totalTabs * 100}%`,
             width: `${totalTabs * 100}%`,
-            // 偏移量
             transform: `translateX(-${(currentIndex / totalTabs) * 100}%)`,
             transform: `translateX(-${(currentIndex / totalTabs) * 100}%)`,
             transition: 'transform 0.7s cubic-bezier(0.34, 1.3, 0.64, 1)'
             transition: 'transform 0.7s cubic-bezier(0.34, 1.3, 0.64, 1)'
           }}
           }}
         >
         >
           {TAB_ORDER.map((tab) => (
           {TAB_ORDER.map((tab) => (
-            <div
-              key={tab}
-              className="shrink-0 flex justify-center pb-12"
-              // 每个页面的宽度是总宽度的 1/6
-              style={{ width: `${100 / totalTabs}%` }}
-            >
-              <div className="w-full max-w-[1600px] px-6 py-6">
-                {children(tab)}
+            <div key={tab} className="shrink-0 h-full overflow-y-auto" style={{ width: `${100 / totalTabs}%` }}>
+              <div className="flex justify-center pb-12">
+                <div className="w-full max-w-[1600px] px-6 py-6">
+                  {children(tab)}
+                </div>
               </div>
               </div>
             </div>
             </div>
           ))}
           ))}
@@ -136,4 +108,4 @@ export function MainLayout({ children }: MainLayoutProps) {
       </main>
       </main>
     </div>
     </div>
   );
   );
-}
+}

+ 4 - 1
knowhub/frontend/src/main.tsx

@@ -1,10 +1,13 @@
 import { StrictMode } from 'react'
 import { StrictMode } from 'react'
 import { createRoot } from 'react-dom/client'
 import { createRoot } from 'react-dom/client'
+import { BrowserRouter } from 'react-router-dom'
 import './index.css'
 import './index.css'
 import App from './App.tsx'
 import App from './App.tsx'
 
 
 createRoot(document.getElementById('root')!).render(
 createRoot(document.getElementById('root')!).render(
   <StrictMode>
   <StrictMode>
-    <App />
+    <BrowserRouter>
+      <App />
+    </BrowserRouter>
   </StrictMode>,
   </StrictMode>,
 )
 )

+ 11 - 1
knowhub/frontend/src/pages/Dashboard.tsx

@@ -67,15 +67,24 @@ function CompactListCard({ data, type, onDrillDown }: { data: any, type: 'req' |
 }
 }
 
 
 
 
-export function Dashboard() {
+export function Dashboard({ pendingNode, onPendingConsumed }: { pendingNode?: string | null, onPendingConsumed?: () => void }) {
   type NavItem = { type: 'node' | 'req' | 'cap' | 'tool' | 'know', data: any };
   type NavItem = { type: 'node' | 'req' | 'cap' | 'tool' | 'know', data: any };
   const [treeData, setTreeData] = useState<any>(null);
   const [treeData, setTreeData] = useState<any>(null);
   const [selectedNode, setSelectedNode] = useState<any>(null);
   const [selectedNode, setSelectedNode] = useState<any>(null);
   const [navStack, setNavStack] = useState<NavItem[]>([]);
   const [navStack, setNavStack] = useState<NavItem[]>([]);
+  const [nameToNodeMap, setNameToNodeMap] = useState<Record<string, any>>({});
   const [dbData, setDbData] = useState<{ reqs: any[], caps: any[], tools: any[], know: any[] }>({
   const [dbData, setDbData] = useState<{ reqs: any[], caps: any[], tools: any[], know: any[] }>({
     reqs: [], caps: [], tools: [], know: []
     reqs: [], caps: [], tools: [], know: []
   });
   });
 
 
+  // 处理来自其他页面的跳转请求
+  useEffect(() => {
+    if (pendingNode && nameToNodeMap[pendingNode]) {
+      setSelectedNode(nameToNodeMap[pendingNode]);
+      onPendingConsumed?.();
+    }
+  }, [pendingNode, nameToNodeMap]);
+
   useEffect(() => {
   useEffect(() => {
     if (selectedNode) setNavStack([{ type: 'node', data: selectedNode }]);
     if (selectedNode) setNavStack([{ type: 'node', data: selectedNode }]);
     else setNavStack([]);
     else setNavStack([]);
@@ -162,6 +171,7 @@ export function Dashboard() {
           });
           });
         };
         };
         buildNameMap([data]);
         buildNameMap([data]);
+        setNameToNodeMap(nameToNode);
 
 
         reqs.forEach((r: any) => {
         reqs.forEach((r: any) => {
           (r.source_nodes || []).forEach((sn: any) => {
           (r.source_nodes || []).forEach((sn: any) => {

+ 133 - 163
knowhub/frontend/src/pages/Relations.tsx

@@ -3,194 +3,164 @@ import { Target, Cpu, Wrench, ListTree, Waypoints } from 'lucide-react';
 import { getRequirements, getCapabilities, getTools } from '../services/api';
 import { getRequirements, getCapabilities, getTools } from '../services/api';
 import { cn } from '../lib/utils';
 import { cn } from '../lib/utils';
 
 
-export function Relations() {
+export function Relations({ onNavigateToDashboard }: { onNavigateToDashboard?: (nodeName: string) => void }) {
   const [reqs, setReqs] = useState<any[]>([]);
   const [reqs, setReqs] = useState<any[]>([]);
   const [caps, setCaps] = useState<any[]>([]);
   const [caps, setCaps] = useState<any[]>([]);
   const [tools, setTools] = useState<any[]>([]);
   const [tools, setTools] = useState<any[]>([]);
-  const [isLoading, setIsLoading] = useState(true);
-
-  // 记录当前点击高亮的节点 ID (格式如 "req:123" 或 "cap:456")
-  const [activeNode, setActiveNode] = useState<string | null>(null);
+  const [activeId, setActiveId] = useState<string | null>(null);
 
 
   useEffect(() => {
   useEffect(() => {
-    setIsLoading(true);
-    Promise.all([
-      getRequirements(1000),
-      getCapabilities(1000),
-      getTools(1000)
-    ]).then(([r, c, t]) => {
-      // --- 关键调试代码 ---
-      console.log("接口返回原始数据:", { r, c, t });
-
-      // 兼容两种返回格式:
-      // 1. { results: [...] }
-      // 2. 直接就是一个数组 [...]
-      const extract = (data: any) => (Array.isArray(data) ? data : (data?.results || []));
-
-      setReqs(extract(r));
-      setCaps(extract(c));
-      setTools(extract(t));
-      setIsLoading(false);
-    }).catch(err => {
-      console.error("接口请求彻底失败:", err);
-      setIsLoading(false); // 必须停掉转圈,否则用户没法用
-    });
+    Promise.all([getRequirements(1000), getCapabilities(1000), getTools(1000)])
+      .then(([r, c, t]) => {
+        setReqs(r.results || []);
+        setCaps(c.results || []);
+        setTools(t.results || []);
+      });
   }, []);
   }, []);
 
 
-  // 【核心算法 1:构建双向连通图】
-  // 把分散的表结构组合成一个内存图 (Adjacency List),便于递归寻路
-  const graph = useMemo(() => {
-     const g = new Map<string, string[]>();
-     const addEdge = (n1: string, n2: string) => {
-       if (!g.has(n1)) g.set(n1, []);
-       if (!g.has(n2)) g.set(n2, []);
-       // 建立无向图的双向连接
-       if (!g.get(n1)!.includes(n2)) g.get(n1)!.push(n2);
-       if (!g.get(n2)!.includes(n1)) g.get(n2)!.push(n1);
-     };
-
-     // 从 能力 寻找其关联的 需求
-     caps.forEach(c => {
-       (c.requirement_ids || []).forEach((rid: string) => addEdge(`cap:${c.id}`, `req:${rid}`));
-     });
-     // 从 工具 寻找其关联的 能力
-     tools.forEach(t => {
-       (t.capability_ids || []).forEach((cid: string) => addEdge(`tool:${t.id}`, `cap:${cid}`));
-     });
-     return g;
-  }, [caps, tools]);
-
-  // 【核心算法 2:BFS 广度优先搜索】
-  // 当选中一个节点时,找出网络中所有与其直接或间接连通的节点集合
-  const connectedNodes = useMemo(() => {
-     if (!activeNode) return new Set<string>();
-     const visited = new Set<string>();
-     const queue = [activeNode];
-     visited.add(activeNode);
-
-     while (queue.length > 0) {
-       const curr = queue.shift()!;
-       const neighbors = graph.get(curr) || [];
-       for (const n of neighbors) {
-         if (!visited.has(n)) {
-           visited.add(n);
-           queue.push(n);
-         }
-       }
-     }
-     return visited;
-  }, [activeNode, graph]);
+  // 核心:构建全网映射表
+  const adjacencyMap = useMemo(() => {
+    const map = new Map<string, Set<string>>();
+    const add = (a: string, b: string) => {
+      if (!map.has(a)) map.set(a, new Set());
+      if (!map.has(b)) map.set(b, new Set());
+      map.get(a)!.add(b);
+      map.get(b)!.add(a);
+    };
+
+    reqs.forEach(r => {
+      (r.capability_ids || []).forEach((cid: string) => add(`req:${r.id}`, `cap:${cid}`));
+    });
+    caps.forEach(c => {
+      (c.tool_ids || c.capability_ids || []).forEach((tid: string) => add(`cap:${c.id}`, `tool:${tid}`));
+      (c.requirement_ids || []).forEach((rid: string) => add(`cap:${c.id}`, `req:${rid}`));
+    });
+    tools.forEach(t => {
+      (t.capability_ids || []).forEach((cid: string) => add(`tool:${t.id}`, `cap:${cid}`));
+    });
+    return map;
+  }, [reqs, caps, tools]);
+
+  // 列顺序:只允许向右(序号更大)方向遍历,防止回路
+  const colOrder: Record<string, number> = { req: 0, proc: 1, cap: 2, tool: 3 };
+  const getColType = (nodeId: string) => nodeId.split(':')[0];
+
+  // 核心:有向 BFS,携带方向状态,禁止 U 形回头
+  // dir: 0=起点(双向), 1=向右, -1=向左
+  const relatedIds = useMemo(() => {
+    if (!activeId) return new Set<string>();
+    const visited = new Set<string>([activeId]);
+    const queue: [string, number][] = [[activeId, 0]];
+    while (queue.length > 0) {
+      const [u, dir] = queue.shift()!;
+      const uOrder = colOrder[getColType(u)] ?? -1;
+      const neighbors = adjacencyMap.get(u) || new Set();
+      neighbors.forEach(v => {
+        if (visited.has(v)) return;
+        const vOrder = colOrder[getColType(v)] ?? -1;
+        const goRight = vOrder > uOrder;
+        const goLeft = vOrder < uOrder;
+        const allowed = dir === 0 || (dir === 1 && goRight) || (dir === -1 && goLeft);
+        if (allowed) {
+          visited.add(v);
+          queue.push([v, goRight ? 1 : -1]);
+        }
+      });
+    }
+    return visited;
+  }, [activeId, adjacencyMap]);
+
+  // 高亮元素上浮:有选中时把相关项排到前面
+  const sortedItems = (items: any[], type: string) => {
+    if (!activeId) return items;
+    const activeType = activeId.split(':')[0];
+    if (type === activeType) return items; // 被点击的列不重排
+    return [...items].sort((a, b) => {
+      const aRel = relatedIds.has(`${type}:${a.id}`) ? 0 : 1;
+      const bRel = relatedIds.has(`${type}:${b.id}`) ? 0 : 1;
+      return aRel - bRel;
+    });
+  };
 
 
-  // 渲染单个卡片的通用函数
-  const renderCard = (type: string, item: any, Icon: any, colorClass: string, bgClass: string, borderClass: string) => {
+  const renderCard = (type: string, item: any, Icon: any) => {
     const nodeId = `${type}:${item.id}`;
     const nodeId = `${type}:${item.id}`;
-    const isSelected = activeNode === nodeId; // 自己被点击
-    const isConnected = connectedNodes.has(nodeId); // 自己属于关联网络中的一员
-    const isDimmed = activeNode !== null && !isConnected; // 自己是个局外人(需要变暗)
+    const isSelected = activeId === nodeId;
+    const isRelated = !activeId || relatedIds.has(nodeId);
+    const dimmed = !!activeId && !isRelated;
+
+    // 需求列:展示 source_nodes 树节点标签(和 Dashboard 颜色一致)
+    const sourceNodeTags: string[] = type === 'req'
+      ? (item.source_nodes || []).slice(0, 4).map((sn: any) =>
+          typeof sn === 'object' ? (sn.node_name || sn.name || '') : sn
+        ).filter(Boolean)
+      : [];
+    const extraCount = type === 'req' ? Math.max(0, (item.source_nodes || []).length - 4) : 0;
 
 
     return (
     return (
       <div
       <div
-        key={item.id}
-        onClick={() => setActiveNode(isSelected ? null : nodeId)}
+        key={nodeId}
+        onClick={() => setActiveId(isSelected ? null : nodeId)}
         className={cn(
         className={cn(
-          "p-3 rounded-xl border transition-all duration-300 cursor-pointer text-left",
-          // 状态 1:当前亲自点击选中的主角
-          isSelected ? `ring-2 ring-offset-1 ${borderClass} bg-white shadow-md scale-[1.02]` :
-          // 状态 2:主角的兄弟姐妹(被点亮)
-          isConnected ? `${bgClass} ${borderClass} bg-white opacity-100` :
-          // 状态 3:无关人员(变暗,黑白)
-          isDimmed ? "opacity-30 grayscale border-slate-100 bg-slate-50 hover:opacity-60" :
-          // 状态 4:没有任何选中时的默认状态
-          `bg-white border-slate-200 hover:${borderClass} hover:shadow-md`
+          "p-3 rounded-xl cursor-pointer mb-2 select-none bg-white transition-all",
+          isSelected
+            ? "border border-orange-400 shadow-[0_0_0_1px_rgba(251,146,60,0.7)]"
+            : isRelated && activeId
+            ? "border border-orange-300"
+            : dimmed
+            ? "border border-transparent opacity-20 grayscale scale-95"
+            : "border border-transparent"
         )}
         )}
       >
       >
-        <div className="flex items-start gap-2 mb-1.5">
-          <Icon size={14} className={cn("mt-0.5 shrink-0", isSelected || isConnected ? colorClass : "text-slate-400")} />
-          <span className={cn("text-xs font-bold leading-snug line-clamp-2", isSelected || isConnected ? "text-slate-800" : "text-slate-600")}>
-            {item.name || item.description || item.id}
-          </span>
-        </div>
-        {item.introduction && type === 'tool' && (
-           <div className="text-[10px] text-slate-500 line-clamp-2 leading-relaxed pl-5">
-            {item.introduction}
+        <div className="flex items-start gap-2">
+          <Icon size={14} className={cn("mt-0.5 shrink-0", isSelected ? "text-orange-500" : "text-slate-400")} />
+          <div className="min-w-0">
+            <div className={cn("text-xs font-bold leading-snug", isSelected ? "text-orange-800" : "text-slate-700")}>
+              {item.name || item.description || item.id}
+            </div>
+            {sourceNodeTags.length > 0 && (
+              <div className="flex flex-wrap gap-1 mt-1.5" onClick={e => e.stopPropagation()}>
+                {sourceNodeTags.map((name: string) => (
+                  <span
+                    key={name}
+                    onClick={() => onNavigateToDashboard?.(name)}
+                    className="text-[9px] px-1.5 py-0.5 rounded-md bg-blue-100 text-blue-700 font-medium truncate max-w-[120px] cursor-pointer hover:bg-blue-200 transition-colors"
+                  >
+                    {name}
+                  </span>
+                ))}
+                {extraCount > 0 && (
+                  <span className="text-[9px] px-1.5 py-0.5 rounded-md bg-slate-100 text-slate-400">
+                    +{extraCount}
+                  </span>
+                )}
+              </div>
+            )}
           </div>
           </div>
-        )}
-        <div className="text-[9px] text-slate-400 font-mono mt-2 pl-5">ID: {item.id.substring(0,6)}...</div>
+        </div>
       </div>
       </div>
     );
     );
   };
   };
 
 
-  if (isLoading) {
-     return <div className="flex justify-center items-center h-64"><div className="w-8 h-8 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin"></div></div>;
-  }
-
   return (
   return (
-    <div className="flex flex-col h-[calc(100vh-120px)] animate-in fade-in duration-500">
-      {/* 顶部标题区 */}
-      <div className="flex justify-between items-end shrink-0 mb-6">
-        <div>
-          <h1 className="text-2xl font-black text-slate-900 mb-1 flex items-center gap-2">
-            <Waypoints size={24} className="text-indigo-600"/> 全局关系追溯
-          </h1>
-          <p className="text-slate-500 text-sm">选择任意一列的节点,即可递归高亮展示整条产业链(直接与间接关联要素)。</p>
-        </div>
-        {activeNode && (
-          <button
-            onClick={() => setActiveNode(null)}
-            className="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-600 text-sm font-bold rounded-xl transition-colors shadow-sm"
-          >
-            清除选中关系
-          </button>
-        )}
+    <div className="flex flex-col h-[calc(100vh-156px)] animate-in fade-in duration-500">
+      <div className="flex justify-between items-center mb-6 shrink-0">
+        <h1 className="text-2xl font-black text-slate-900 flex items-center gap-2">
+          <Waypoints className="text-indigo-600" /> 全局关系追溯
+        </h1>
+        {activeId && <button onClick={() => setActiveId(null)} className="text-xs bg-slate-200 px-3 py-1.5 rounded-lg font-bold hover:bg-slate-300">清除选中</button>}
       </div>
       </div>
 
 
-      {/* 4 列主体表格 */}
       <div className="grid grid-cols-4 gap-4 flex-1 min-h-0">
       <div className="grid grid-cols-4 gap-4 flex-1 min-h-0">
-
-        {/* 第 1 列:需求 */}
-        <div className="bg-slate-50/50 border border-slate-200 rounded-2xl flex flex-col overflow-hidden">
-          <div className="bg-indigo-50 px-4 py-3 border-b border-indigo-100 font-bold text-indigo-800 flex items-center justify-between shrink-0">
-            <div className="flex items-center gap-2"><Target size={16}/> 业务需求</div>
-            <span className="text-xs bg-white px-2 py-0.5 rounded-full text-indigo-600 shadow-sm">{reqs.length}</span>
-          </div>
-          <div className="p-3 overflow-y-auto custom-scrollbar flex-1 space-y-2">
-            {reqs.map(r => renderCard('req', r, Target, 'text-indigo-600', 'bg-indigo-50/40', 'border-indigo-400'))}
+        {[ {t:'req', l:'业务需求', i:Target, d:reqs}, {t:'proc', l:'生产工序', i:ListTree, d:[]}, {t:'cap', l:'原子能力', i:Cpu, d:caps}, {t:'tool', l:'执行工具', i:Wrench, d:tools} ].map(col => (
+          <div key={col.t} className="flex flex-col h-full bg-slate-100/50 rounded-2xl border border-slate-200 overflow-hidden">
+            <div className="px-4 py-3 font-bold text-slate-700 border-b bg-slate-200/50 flex justify-between">
+              {col.l} <span className="text-slate-400">{col.d.length}</span>
+            </div>
+            <div className="flex-1 overflow-y-auto p-3 custom-scrollbar">
+              {sortedItems(col.d, col.t).map(i => renderCard(col.t, i, col.i))}
+            </div>
           </div>
           </div>
-        </div>
-
-        {/* 第 2 列:工序(暂空) */}
-        <div className="bg-slate-50/50 border border-slate-200 rounded-2xl flex flex-col overflow-hidden opacity-90">
-          <div className="bg-slate-200 px-4 py-3 border-b border-slate-300 font-bold text-slate-700 flex items-center justify-between shrink-0">
-            <div className="flex items-center gap-2"><ListTree size={16}/> 生产工序</div>
-            <span className="text-xs bg-white px-2 py-0.5 rounded-full text-slate-500 shadow-sm">0</span>
-          </div>
-          <div className="p-4 overflow-y-auto custom-scrollbar flex-1 flex flex-col items-center justify-center text-slate-400 text-sm font-medium border-2 border-dashed border-slate-200 m-3 rounded-xl bg-slate-50/50">
-            预留扩展位,暂无数据
-          </div>
-        </div>
-
-        {/* 第 3 列:能力 */}
-        <div className="bg-slate-50/50 border border-slate-200 rounded-2xl flex flex-col overflow-hidden">
-          <div className="bg-emerald-50 px-4 py-3 border-b border-emerald-100 font-bold text-emerald-800 flex items-center justify-between shrink-0">
-            <div className="flex items-center gap-2"><Cpu size={16}/> 原子能力</div>
-            <span className="text-xs bg-white px-2 py-0.5 rounded-full text-emerald-600 shadow-sm">{caps.length}</span>
-          </div>
-          <div className="p-3 overflow-y-auto custom-scrollbar flex-1 space-y-2">
-            {caps.map(c => renderCard('cap', c, Cpu, 'text-emerald-600', 'bg-emerald-50/40', 'border-emerald-400'))}
-          </div>
-        </div>
-
-        {/* 第 4 列:工具 */}
-        <div className="bg-slate-50/50 border border-slate-200 rounded-2xl flex flex-col overflow-hidden">
-          <div className="bg-amber-50 px-4 py-3 border-b border-amber-100 font-bold text-amber-800 flex items-center justify-between shrink-0">
-            <div className="flex items-center gap-2"><Wrench size={16}/> 执行工具</div>
-            <span className="text-xs bg-white px-2 py-0.5 rounded-full text-amber-600 shadow-sm">{tools.length}</span>
-          </div>
-          <div className="p-3 overflow-y-auto custom-scrollbar flex-1 space-y-2">
-             {tools.map(t => renderCard('tool', t, Wrench, 'text-amber-600', 'bg-amber-50/40', 'border-amber-400'))}
-          </div>
-        </div>
-
+        ))}
       </div>
       </div>
     </div>
     </div>
   );
   );

+ 0 - 1
knowhub/internal_tools/__init__.py

@@ -10,7 +10,6 @@ KnowHub 内部工具
 # 重新导出原有工具供 Knowledge Manager 使用
 # 重新导出原有工具供 Knowledge Manager 使用
 from agent.tools.builtin.knowledge import (
 from agent.tools.builtin.knowledge import (
     knowledge_search,
     knowledge_search,
-    knowledge_save,
     knowledge_list,
     knowledge_list,
     knowledge_update,
     knowledge_update,
     knowledge_batch_update,
     knowledge_batch_update,

+ 362 - 0
knowhub/research/outdoor_character_workflow_summary_20260409.md

@@ -0,0 +1,362 @@
+# 户外场景人物活动照片生成工作流合集
+
+**调研日期**: 2026-04-09  
+**调研渠道**: 小红书、X (Twitter)、YouTube  
+**提取成果**: 6种完整工作流方案,12个工具,7个教程资源
+
+---
+
+## 一、核心工作流方案
+
+### Workflow 1: OpenArt Character Builder + 多场景一致性工作流
+
+**适用场景**: 需要保持角色一致性,在多个户外场景中生成人物活动照片
+
+**工具链**: OpenArt AI Character Builder → Nano Banana 2 / Seedance 2.0 → Kling 3.0
+
+**步骤拆解**:
+
+#### Step 1: 创建角色基础
+- **输入**: 选择风格(Style)、性别(Gender)、种族(Ethnicity)、年龄(Age)
+- **工具**: OpenArt Character Builder
+- **输出**: 锁定角色DNA的基础图像(480种组合可选)
+- **说明**: 无需编写复杂提示词,通过UI选择锁定角色特征
+
+#### Step 2: 生成角色参考表 (Character Reference Sheet)
+- **输入**: 上传Step 1生成的角色图像
+- **工具**: Nano Banana 2 / Seedance 2.0
+- **提示词**:
+```
+Create a professional character reference sheet based strictly on the uploaded reference image. 
+Use a clean, neutral plain background. Arrange into two horizontal rows:
+- Top row: four full-body standing views – front, left profile, right profile, back
+- Bottom row: Four close-up portraits – front, left profile, right profile, back
+Maintain perfect identity consistency across every panel.
+```
+- **输出**: 多视角角色参考表(8个面板)
+
+#### Step 3: 场景生成
+- **输入**: 角色参考表 + 场景提示词
+- **工具**: Seedance 2.0 / Kling 3.0
+- **提示词结构**:
+```json
+{
+  "subject": "角色描述",
+  "environment": {
+    "setting": "户外场景 (如:spring alpine meadow, cherry blossom street)",
+    "time_of_day": "golden hour / soft daylight",
+    "lighting": "natural lighting, volumetric light shafts"
+  },
+  "camera": {
+    "lens": "85mm / wide-angle"
+  },
+  "lighting": {
+    "type": "soft diffused daylight",
+    "effect": "rim lighting, gentle shadows"
+  }
+}
+```
+- **输出**: 角色在户外场景中的图像/视频
+
+#### Step 4: 视频生成 (可选)
+- **输入**: 起始帧 + 结束帧
+- **工具**: Seedance 2.0
+- **提示词**: "Show me what happens in between. USE MULTIPLE CAMERA ANGLES"
+- **输出**: 多镜头角度的过渡视频
+
+**关键技术要点**:
+- 角色一致性保证:使用Character Builder锁定DNA + 生成多视角参考表 + 所有场景使用相同参考图
+- 户外场景提示词要素:golden hour, volumetric light shafts, rim lighting, subsurface scattering
+
+**来源**: 小红书/X/YouTube调研 (@thetripathi58, @heyDhavall, @MaAyyoub)
+
+---
+
+### Workflow 2: ComfyUI + Blender 户外场景生成工作流
+
+**适用场景**: 需要高度可控的3D场景建模和电影级渲染效果
+
+**工具链**: Blender → ComfyUI → Z-Image Turbo → LTX-2.3 → ComfyUI-VideoColorGrading
+
+**步骤拆解**:
+
+#### Step 1: 场景建模
+- **输入**: 文本提示词或概念图
+- **工具**: Blender (在ComfyUI内运行)
+- **输出**: 3D场景模型(支持视口预览)
+
+#### Step 2: 渲染控制
+- **输入**: 3D模型 + 相机路径
+- **工具**: ComfyUI + Blender集成节点
+- **输出**: 渲染帧序列
+
+#### Step 3: 图像生成增强
+- **输入**: 渲染帧
+- **工具**: Z-Image Turbo
+- **输出**: 增强细节的图像
+
+#### Step 4: 视频生成
+- **输入**: 增强后的图像序列
+- **工具**: LTX-2.3 + WanAnimate
+- **输出**: 最终视频
+
+#### Step 5: 颜色分级
+- **输入**: 生成的视频 + 参考图像
+- **工具**: ComfyUI-VideoColorGrading (Kijai)
+- **流程**: 从参考图像生成3D color LUT → 两阶段扩散过程对齐源视频帧与参考风格 → 应用时间一致性颜色分级
+- **输出**: 颜色分级后的最终视频
+
+**优势**:
+- 完全可控的3D场景布局
+- 支持精确的相机路径设计
+- 专业的颜色分级流程
+
+**难度等级**: ⭐⭐⭐⭐⭐(需要Blender和ComfyUI专业知识)
+
+**来源**: X/YouTube调研
+
+---
+
+### Workflow 3: ComfyUI + BrushNet 婚纱摄影外景工作流
+
+**适用场景**: 婚纱摄影、商业人像外景合成
+
+**工具链**: ComfyUI → BrushNet → IC-Light → Segment Anything → IPAdapter
+
+**核心流程**:
+1. **模型及原图加载** → 设置宽高比、缩放模式
+2. **遮罩生成** → Segment Anything 自动分割人物
+3. **BrushNet 局部重绘背景**
+   - 优势:对遮罩边界控制优秀,不破坏主体
+   - 透视关系协调
+4. **IC-Light 打光融合** → 使人物与背景光影统一
+5. **高清修复放大** → 恢复原图尺寸
+6. **PS 细节还原** → 图层和遮罩处理
+
+**关键节点**: `comfyui-brushnet`, `comfyui-inpaint-nodes`, `comfyui-ipadapter_plus`
+
+**来源**: 小红书 (帖子: 婚纱摄影外景自由)
+
+---
+
+### Workflow 4: ComfyUI 人像摄影工作流 (SD1.5)
+
+**适用场景**: 换装、换背景、保持面部特征的人像创作
+
+**工具链**: ComfyUI → SD1.5 → ControlNet (OpenPose) → IPAdapter → LoRA
+
+**核心思路**:
+1. **蒙版遮罩 + Latent 复合** → 重绘目标区域,保证光影效果
+2. **ControlNet 固定姿态** → OpenPose 控制人物姿势
+3. **IPAdapter + WD14 标签提取** → 风格迁移
+
+**工作流特点**:
+- 保持面部特征不变
+- 可换装、换背景
+- 支持多 ControlNet 叠加 (Depth, Canny, OpenPose)
+
+**来源**: 小红书 (帖子: comfyui 人像摄影工作流)
+
+---
+
+### Workflow 5: Kontext + MidJourney 场景及角色一致性工作流
+
+**适用场景**: 叙事动画、多镜头分镜、大场景多主体融合
+
+**工具链**: Kontext (Flux) → MidJourney → GPT
+
+**5步流程**:
+1. **准备三视图** → 角色/关键道具的正面、侧面、背面图
+2. **准备分镜草图** → 手绘构图规划
+3. **做主场景氛围空镜** → 确定整体色调和氛围
+4. **做分镜场景空镜** → MJ 生成,使用 `--sref` 引用氛围图
+5. **主体与场景融合** → Kontext 双图/多图模式融合
+
+**关键技巧**:
+- 大场景分区处理:先融合局部,再拼合到全景
+- 小角度机位切换可直接用 Kontext,大角度需单独生成
+- 建议使用 Kontext Pro/Max 版本,开源 dev 版本效果有限
+
+**来源**: 小红书 (帖子: 用Kontext + MJ 控制场景及角色一致性)
+
+---
+
+### Workflow 6: Nano Banana + Kling 户外人物行走场景
+
+**适用场景**: 街拍松弛感写真、人物行走视频
+
+**工具链**: Nano Banana Pro → Kling 3.0 → Topaz Gigapixel AI → Photoshop
+
+**街拍松弛感写真流程**:
+1. **上传参考照片** → 清晰半身照
+2. **使用详细提示词** (见下方模板)
+3. **生成多视角变体** → 选择最佳
+4. **Topaz 放大** → 4K 输出
+5. **PS 合成清理** → 去除瑕疵
+6. **Kling 动画** → 首尾帧控制生成行走视频
+
+**提示词模板** (精简版):
+```
+参考我上传的照片,输出高分辨率彩色时尚人像。
+主体:年轻女性,长发微卷,米色宽松衬衫外套,白色蕾丝短裙,黑色皮包,手持咖啡。
+姿态:三分之四侧身,左脚微前,右手轻抬整理发丝,眼神自信直视镜头。
+环境:高端城市街区咖啡店门口,暖色橱窗虚化,石材路面反光。
+光线:阴天自然柔光,侧后缘光勾勒发丝和包带。
+摄影:全画幅 85mm 人像镜头,f/1.8,浅景深,人物位于右侧三分之一处。
+细节:肌肤保留毛孔,发丝清晰,皮包纹理可见,轻微胶片颗粒。
+比例:4:5
+```
+
+**来源**: 小红书 (帖子: Nano Banana生成街拍松弛感写真) + YouTube (Uvq85gcMSE0, SeuI2zIWQJ0)
+
+---
+
+## 二、工具清单汇总
+
+| 工具名称 | 类别 | 核心用途 | 状态 |
+|---------|------|---------|------|
+| **OpenArt Character Builder** | 角色生成 | 4步锁定角色DNA (风格/性别/种族/年龄) | 已验证 |
+| **Nano Banana / Nano Banana Pro** | 图像生成 | 角色参考表、多视角生成、街拍写真 | 已验证 |
+| **Seedance 2.0** | 图像/视频生成 | 角色一致性、场景生成、视频过渡 | 已验证 |
+| **Kling 3.0** | 视频生成 | 高质量视频、首尾帧控制、多镜头角度 | 已验证 |
+| **ComfyUI** | 工作流编排 | 节点式工作流,集成多种模型 | 已验证 |
+| **BrushNet** | 局部重绘 | 背景替换,遮罩边界控制优秀 | 已验证 |
+| **IC-Light** | 光影融合 | 人物与背景打光统一 | 已验证 |
+| **ControlNet** | 姿态控制 | OpenPose/Depth/Canny 固定构图 | 已验证 |
+| **IPAdapter** | 风格迁移 | 参考图风格提取 | 已验证 |
+| **Kontext (Flux)** | 图像融合 | 多主体场景融合,角色一致性 | 已验证 |
+| **MidJourney** | 场景生成 | 氛围空镜、分镜场景生成 | 已验证 |
+| **Topaz Gigapixel AI** | 图像放大 | 4K 无损放大 | 已验证 |
+| **Photoshop** | 后期处理 | 遮罩清理、细节还原、图层合成 | 已验证 |
+| **Segment Anything** | 图像分割 | 自动人物抠图 | 已验证 |
+| **Blender** | 3D建模 | 场景建模,可与ComfyUI集成 | 已验证 |
+| **Z-Image Turbo** | 图像增强 | 提升渲染帧质感和细节 | 已验证 |
+| **LTX-2.3** | 视频生成 | 图像序列转视频 | 已验证 |
+| **WanAnimate** | 视频生成 | 视频生成和动画 | 已验证 |
+| **ComfyUI-VideoColorGrading** | 视频后期 | 基于参考图的颜色分级 | 已验证 |
+| **ChatGPT** | 提示词优化 | 构建光线控制等专业提示词 | 已验证 |
+| **PixPrettyAI** | 图像生成平台 | 托管Nano Banana 2等模型 | 已验证 |
+
+---
+
+## 三、关键提示词模板
+
+### 角色参考表生成 (Nano Banana 2)
+```
+Create a professional character reference sheet based strictly on the uploaded reference image. 
+Use a clean, neutral plain background. Arrange into two horizontal rows:
+- Top row: four full-body standing views – front, left profile, right profile, back
+- Bottom row: Four close-up portraits – front, left profile, right profile, back
+Maintain perfect identity consistency across every panel.
+```
+
+### 户外场景生成 (Seedance 2.0 / Kling)
+```json
+{
+  "subject": "年轻女性,长发微卷,米色衬衫外套,白色蕾丝裙",
+  "environment": {
+    "setting": "spring alpine meadow / cherry blossom street / urban cafe",
+    "time_of_day": "golden hour / soft daylight",
+    "lighting": "natural lighting, volumetric light shafts",
+    "elements": ["flowers", "trees", "mountains", "cobblestone street"]
+  },
+  "camera": {
+    "lens": "85mm portrait / wide-angle",
+    "movement": "slow tracking / pan"
+  },
+  "lighting": {
+    "type": "soft diffused daylight",
+    "effect": "rim lighting, gentle shadows"
+  }
+}
+```
+
+### 自然光线人像提示词结构
+```
+A high-contrast cinematic portrait with dramatic rim lighting from behind.
+Subject: [描述人物特征、服装、动作]
+Lighting: 
+  - Strong direct light illuminating one side of face
+  - Clear highlights and definition
+  - Softly shadowed opposite side
+  - Light source direction clearly visible
+Environment: Dark abstract bokeh background / outdoor park setting
+Camera: 85mm lens, shallow depth of field
+Style: Professional DSLR portrait, film grain, vintage feel
+```
+
+### 光线类型关键词对照表
+| 光线类型 | 提示词关键词 | 适用场景 |
+|---------|------------|---------|
+| Rim Lighting | dramatic rim lighting from behind | 人像特写、逆光场景 |
+| Golden Hour | golden hour lighting, warm tones | 温馨场景、日落场景 |
+| Volumetric Light | volumetric light shafts, god rays | 森林、有遮挡的场景 |
+| Soft Diffused | soft diffused daylight, overcast | 日常场景、阴天场景 |
+| Subsurface Scattering | subsurface scattering for skin | 人像特写、皮肤质感 |
+
+---
+
+## 四、调研经验总结
+
+### 经验 1: 如何构造有效的搜索关键词
+
+当调研AI生图工作流/工序时,应该将用户需求转换为偏向「教程」、「工作流」、「全套方案」的搜索词,而非单一软件名。
+
+**原因**: 单一软件名搜索结果多为工具介绍,而工作流关键词能直接找到完整的多步工序方案。
+
+**案例**: 本次调研「户外场景人物活动照片」需求,构造了5个关键词:
+1. AI生图 户外人物 工作流 教程
+2. Stable Diffusion 真实场景人物 完整流程
+3. AI绘画 自然光线 人物活动 步骤详解
+4. ComfyUI 户外场景 人物生成 workflow
+5. AI生图 真实背景人物合成 全套方案
+
+每个关键词都包含「工作流/流程/方案/教程」等工序导向词,成功从300条结果中提取19个完整Workflow。
+
+### 经验 2: 多渠道调研的并行执行策略
+
+当需要多渠道(小红书、X、YouTube)调研同一主题时,应该为每个渠道启动独立的research子agent并行执行。
+
+**原因**: 
+- 各渠道数据结构不同,独立agent可针对性处理
+- 并行执行大幅缩短调研时间
+
+**案例**: 本次调研同时启动3个agent,分别使用中文关键词(小红书)和英文关键词(X、YouTube),每个agent搜索5个关键词×20条结果,总计300条结果,在约15分钟内完成全部调研并提取19个Workflow。
+
+**关键技巧**:
+1. 为不同渠道准备适配的关键词(中文/英文)
+2. 任务描述中明确要求「只提取多步工序,严格拆解步骤」
+3. 使用deconstruct agent类型自动进行工序拆解
+
+---
+
+## 五、工作流选择建议
+
+| 需求场景 | 推荐工作流 | 理由 |
+|---------|-----------|------|
+| **角色一致性优先** | Workflow 1 (OpenArt + Seedance) | 4步锁定DNA,参考表机制成熟 |
+| **3D场景精确控制** | Workflow 2 (Blender + ComfyUI) | 完全可控的3D布局,专业颜色分级 |
+| **婚纱摄影/商业人像** | Workflow 3 (BrushNet) | 遮罩控制精准,光影融合自然 |
+| **换装/换背景灵活操作** | Workflow 4 (ComfyUI SD1.5) | ControlNet + IPAdapter 组合灵活 |
+| **叙事动画/多镜头** | Workflow 5 (Kontext + MJ) | 分镜规划完善,大场景分区处理 |
+| **街拍松弛感写真** | Workflow 6 (Nano Banana + Kling) | 提示词模板成熟,视频生成流畅 |
+
+---
+
+## 六、教程资源索引
+
+| 来源 | 标题 | 链接 |
+|------|------|------|
+| **小红书** | BrushNet婚纱摄影外景工作流 | https://www.xiaohongshu.com/explore/67b8726e0000000029032412 |
+| **小红书** | ComfyUI人像摄影工作流(SD1.5) | https://www.xiaohongshu.com/explore/66253296000000000302272a |
+| **小红书** | Kontext + MJ场景及角色一致性 | https://www.xiaohongshu.com/explore/687771cf0000000024008e4b |
+| **小红书** | Nano Banana街拍松弛感写真 | https://www.xiaohongshu.com/explore/68e11fa700000000070363c7 |
+| **YouTube** | Add AI People to Your Renders | https://www.youtube.com/watch?v=Uvq85gcMSE0 |
+| **YouTube** | Turn One Image into Multiple Camera Angles | https://www.youtube.com/watch?v=SeuI2zIWQJ0 |
+| **本地缓存** | AI户外工作流合集原始数据 | `/root/Agent/.cache/research/ai_outdoor_workflow_collection_20260409.json` |
+
+---
+
+**文档生成时间**: 2026-04-09 19:38  
+**调研执行Agent**: Librarian Agent  
+**数据状态**: 已整理归档,待入库

+ 59 - 8
knowhub/server.py

@@ -18,7 +18,7 @@ from typing import Optional, List, Dict
 from pathlib import Path
 from pathlib import Path
 from cryptography.hazmat.primitives.ciphers.aead import AESGCM
 from cryptography.hazmat.primitives.ciphers.aead import AESGCM
 
 
-from fastapi import FastAPI, HTTPException, Query, Header, Body, BackgroundTasks
+from fastapi import FastAPI, HTTPException, Query, Header, Body, BackgroundTasks, Request
 from fastapi.responses import HTMLResponse, FileResponse
 from fastapi.responses import HTMLResponse, FileResponse
 from fastapi.staticfiles import StaticFiles
 from fastapi.staticfiles import StaticFiles
 from pydantic import BaseModel, Field
 from pydantic import BaseModel, Field
@@ -2382,13 +2382,56 @@ def delete_requirement(req_id: str):
         raise
         raise
     except Exception as e:
     except Exception as e:
         raise HTTPException(status_code=500, detail=str(e))
         raise HTTPException(status_code=500, detail=str(e))
-@app.get("/")
-def frontend():
-    """KnowHub 管理前端"""
-    index_file = STATIC_DIR / "index.html"
-    if not index_file.exists():
-        return HTMLResponse("<h1>KnowHub Frontend Not Found</h1><p>Please ensure knowhub/frontend/dist/index.html exists. Run 'yarn build' in frontend directory.</p>", status_code=404)
-    return FileResponse(str(index_file))
+# ===== Relation API =====
+
+@app.get("/api/relation/{table_name}")
+async def get_relations(table_name: str, request: Request):
+    """通用关系表查询接口"""
+    allowed_tables = {
+        "capability_knowledge",
+        "capability_tool",
+        "knowledge_relation",
+        "knowledge_resource",
+        "requirement_capability",
+        "requirement_knowledge",
+        "tool_knowledge",
+        "tool_provider"
+    }
+    table_name = table_name.lower()
+    if table_name not in allowed_tables:
+        raise HTTPException(status_code=400, detail="Invalid table name")
+        
+    try:
+        params = dict(request.query_params)
+        
+        where_clauses = []
+        values = []
+        for k, v in params.items():
+            if k in ["limit", "offset"]: continue
+            where_clauses.append(f"{k} = %s")
+            values.append(v)
+        
+        query = f"SELECT * FROM {table_name}"
+        if where_clauses:
+            query += " WHERE " + " AND ".join(where_clauses)
+            
+        limit = int(params.get("limit", 100))
+        query += " LIMIT %s"
+        values.append(limit)
+        
+        cursor = pg_store._get_cursor()
+        try:
+            cursor.execute(query, tuple(values))
+            rows = cursor.fetchall()
+            if not rows:
+                 return {"results": [], "count": 0}
+            colnames = [desc[0] for desc in cursor.description]
+            results = [dict(zip(colnames, row)) for row in rows]
+            return {"results": results, "count": len(results)}
+        finally:
+            cursor.close()
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=str(e))
 
 
 @app.get("/category_tree.json")
 @app.get("/category_tree.json")
 def serve_category_tree():
 def serve_category_tree():
@@ -2398,6 +2441,14 @@ def serve_category_tree():
         return {"error": "Not Found"}
         return {"error": "Not Found"}
     return FileResponse(str(tree_file))
     return FileResponse(str(tree_file))
 
 
+@app.get("/{full_path:path}")
+def frontend(full_path: str):
+    """KnowHub 管理前端 — 所有非 API 路径都返回 index.html,由 React Router 处理"""
+    index_file = STATIC_DIR / "index.html"
+    if not index_file.exists():
+        return HTMLResponse("<h1>KnowHub Frontend Not Found</h1><p>Please ensure knowhub/frontend/dist/index.html exists. Run 'yarn build' in frontend directory.</p>", status_code=404)
+    return FileResponse(str(index_file))
+
 if __name__ == "__main__":
 if __name__ == "__main__":
     import uvicorn
     import uvicorn
     uvicorn.run(app, host="0.0.0.0", port=9999)
     uvicorn.run(app, host="0.0.0.0", port=9999)

+ 104 - 0
knowledge-2026-04-09-ai-image-workflows.json

@@ -0,0 +1,104 @@
+{
+  "knowledge": [
+    {
+      "task": "AI人像皮肤质感修复工作流",
+      "content": "完整工序:\n1. 生成基础人像图片 - 工具:Midjourney / Google Imagen 4 (via Whisk) / Reeve / Flux Pro Ultra - 输入:近景人像提示词(如'close-up portrait, photorealistic, DSLR, 85mm')- 输出:AI生成的基础人像图片(可能存在塑料皮肤问题)\n2. 裁剪人脸区域(如需要)- 工具:图像裁剪工具 - 输入:基础人像图片,将人脸区域裁剪至更近的特写 - 输出:裁剪后的人脸特写图片\n3. 修复AI塑料皮肤质感 - 工具:Enhancor.ai (Skin Realism + Portrait Upscaler) - 输入:AI生成的人像图片,设置皮肤纹理强度、毛孔细节参数 - 输出:具有真实皮肤纹理、毛孔细节的人像图片\n4. 修复眼部瑕疵(可选)- 工具:Enhancor.ai 眼部修复功能 - 输入:修复后的人像图片,指定眼部区域 - 输出:眼部细节完美的人像图片\n5. 超分辨率放大 - 工具:Enhancor.ai Portrait Upscaler - 输入:修复后的人像图片,选择放大倍数(如4x)- 输出:billboard级别的高分辨率人像图片\n适用场景:商业人像摄影、广告牌级别输出、需要真实皮肤质感的AI人像",
+      "types": ["strategy"],
+      "tags": {
+        "source": "YouTube",
+        "domain": "AI图像生成",
+        "focus": "人像、皮肤质感、超分辨率"
+      },
+      "score": 5
+    },
+    {
+      "task": "多视角角色一致性工作流(Nano Banana Pro)",
+      "content": "完整工序:\n1. 创建角色参考表 - 工具:Nano Banana Pro (Higgsfield AI) - 输入:结构化提示词(如'Create a professional character reference sheet with four vertical columns: front view, left profile, right profile, back view. Each column contains full-body view on top and matching close-up portrait below. Photorealistic, DSLR, muted tones.')- 输出:包含四个视角(正面、左侧、右侧、背面)的角色参考表,每个视角包含全身图和近景肖像\n2. 生成多视角近景肖像 - 工具:Nano Banana Pro - 输入:角色参考表作为参考图,指定视角(如'front portrait close-up')- 输出:一致角色的多视角近景肖像图片\n3. 生成生活场景图 - 工具:Nano Banana Pro - 输入:角色参考表,场景描述(如'coffee shop lifestyle shot')- 输出:角色在不同生活场景中的图片\n适用场景:AI虚拟influencer、角色设计、需要多角度一致性的项目",
+      "types": ["strategy"],
+      "tags": {
+        "source": "YouTube",
+        "domain": "AI图像生成",
+        "focus": "角色一致性、多视角、近景肖像"
+      },
+      "score": 5
+    },
+    {
+      "task": "面部表情变换工作流(Canva AI Face Expression)",
+      "content": "完整工序:\n1. 上传基础照片 - 工具:Canva AI Face Expression App - 输入:单张人物照片(正面、清晰面部)- 输出:上传至Canva的基础照片\n2. 选择目标表情 - 工具:Canva AI Face Expression App - 输入:选择预设表情(如微笑、惊讶、严肃、开心等)- 输出:应用表情后的人物图片\n3. 批量生成多表情版本 - 工具:Canva AI Face Expression App - 输入:重复选择不同表情,批量处理 - 输出:同一人物的多种表情图片集合\n适用场景:YouTube缩略图制作、社交媒体内容、需要快速生成多种表情的场景",
+      "types": ["strategy"],
+      "tags": {
+        "source": "YouTube",
+        "domain": "AI图像生成",
+        "focus": "面部表情、批量生成"
+      },
+      "score": 4
+    },
+    {
+      "task": "AI虚拟网红完整工作流(KORA Pro + Enhancor + Lip Sync)",
+      "content": "完整工序:\n1. 生成高质量基础人像 - 工具:KORA Pro (Enhancor) - 输入:详细的人像提示词,包括人物特征、服装、光线、相机参数 - 输出:高真实度的基础人像图片\n2. 构建角色多视角图 - 工具:Nano Banana Pro - 输入:基础人像作为参考,生成多角度角色表 - 输出:包含正面、侧面、背面视角的角色参考表\n3. 修复皮肤质感 - 工具:Enhancor AI V3 Skin Fix - 输入:生成的角色图片,调整皮肤纹理参数 - 输出:具有真实皮肤质感的角色图片\n4. 生成生活场景图 - 工具:Nano Banana Pro - 输入:角色参考表,场景描述(如'studio shot', 'lifestyle scene')- 输出:角色在不同场景中的图片\n5. 唇形同步动画 - 工具:Enhancor Lip Sync V1/V2 - 输入:角色图片 + 自定义音频文件 - 输出:角色说话的视频(带唇形同步)\n适用场景:AI虚拟网红、虚拟主播、需要说话动画的角色",
+      "types": ["strategy"],
+      "tags": {
+        "source": "YouTube",
+        "domain": "AI图像生成",
+        "focus": "虚拟网红、唇形同步、角色一致性"
+      },
+      "score": 5
+    },
+    {
+      "task": "4K超高清AI图像生成工作流(ComfyUI + DyPE)",
+      "content": "完整工序:\n1. 设置ComfyUI工作流 - 工具:ComfyUI (本地安装 via Promptus) - 输入:安装DyPE模型(https://github.com/guyyariv/DyPE)- 输出:配置好的ComfyUI环境\n2. 生成原生4K图像 - 工具:ComfyUI + DyPE模型 - 输入:人像提示词,设置分辨率为4K,选择DyPE采样器 - 输出:原生4K分辨率的AI人像图片\n3. 超分辨率增强(可选)- 工具:ComfyUI 4K超分辨率节点 - 输入:生成的4K图片,选择放大倍数 - 输出:更高分辨率、细节更丰富的图片\n适用场景:需要超高清输出的商业项目、影院级画质需求",
+      "types": ["strategy"],
+      "tags": {
+        "source": "YouTube",
+        "domain": "AI图像生成",
+        "focus": "4K、超分辨率、ComfyUI"
+      },
+      "score": 4
+    },
+    {
+      "task": "角色一致性视频生成工作流(OpenArt + Kling/HeyGen)",
+      "content": "完整工序:\n1. 创建一致角色基础图 - 工具:OpenArt AI - 输入:角色描述提示词,上传参考图(如有)- 输出:高质量的角色基础图片\n2. 生成多视角角色图 - 工具:OpenArt AI - 输入:基础图作为参考,指定不同视角和姿势 - 输出:同一角色的多视角图片集合\n3. 生成视频 - 工具:Kling AI / HeyGen - 输入:角色图片 + 动作/对话描述 - 输出:角色动画视频\n4. 唇形同步(如需要)- 工具:HeyGen - 输入:角色视频 + 音频脚本 - 输出:带完美唇形同步的角色说话视频\n适用场景:AI视频创作、虚拟主播、需要角色一致性的视频项目",
+      "types": ["strategy"],
+      "tags": {
+        "source": "YouTube",
+        "domain": "AI视频生成",
+        "focus": "角色一致性、视频生成、唇形同步"
+      },
+      "score": 5
+    },
+    {
+      "task": "背景虚化效果工作流(Photoshop Lens Blur / DxO Photolab)",
+      "content": "完整工序:\n1. 导入人像图片 - 工具:Photoshop / DxO Photolab 9 - 输入:需要添加背景虚化的人像图片 - 输出:导入的图片\n2. 创建主体选区 - 工具:Photoshop 智能选区 / DxO 自动主体检测 - 输入:选择主体(人物)区域 - 输出:精确的人物选区\n3. 应用镜头虚化效果 - 工具:Photoshop Lens Blur / DxO Photolab 9 Bokeh - 输入:设置虚化强度、光圈形状、焦距参数 - 输出:具有专业镜头虚化效果的图片\n4. 微调边缘过渡 - 工具:Photoshop 蒙版羽化 - 输入:调整选区边缘羽化值 - 输出:自然过渡的虚化效果图片\n适用场景:人像摄影后期、需要背景虚化效果的AI生成图片",
+      "types": ["strategy"],
+      "tags": {
+        "source": "YouTube",
+        "domain": "图像后期处理",
+        "focus": "背景虚化、bokeh、人像后期"
+      },
+      "score": 4
+    }
+  ],
+  "resources": [
+    {
+      "id": "https://github.com/guyyariv/DyPE",
+      "type": "github_repo"
+    }
+  ],
+  "tools": [
+    {"name": "Midjourney", "introduction": "AI图像生成工具,支持高质量人像生成"},
+    {"name": "Google Imagen 4", "introduction": "Google的AI图像生成模型,通过Whisk平台访问"},
+    {"name": "Reeve", "introduction": "AI图像生成工具"},
+    {"name": "Flux Pro Ultra", "introduction": "AI图像生成工具,支持高质量人像"},
+    {"name": "Enhancor.ai", "introduction": "AI人像增强工具,提供皮肤质感修复和超分辨率功能"},
+    {"name": "Nano Banana Pro", "introduction": "Higgsfield AI的角色一致性生成工具"},
+    {"name": "Canva AI Face Expression", "introduction": "Canva的面部表情变换AI应用"},
+    {"name": "KORA Pro", "introduction": "Enhancor的高质量人像生成工具"},
+    {"name": "ComfyUI", "introduction": "本地AI图像生成工作流工具"},
+    {"name": "DyPE", "introduction": "ComfyUI的4K图像生成模型"},
+    {"name": "OpenArt AI", "introduction": "AI图像生成平台"},
+    {"name": "Kling AI", "introduction": "AI视频生成工具"},
+    {"name": "HeyGen", "introduction": "AI视频和唇形同步工具"},
+    {"name": "Photoshop", "introduction": "专业图像编辑软件"},
+    {"name": "DxO Photolab 9", "introduction": "专业照片处理软件,支持镜头虚化效果"}
+  ]
+}

+ 398 - 0
knowledge/ComfyUI_画中画分阶段生成工作流.md

@@ -0,0 +1,398 @@
+# ComfyUI 画中画分阶段生成工作流
+
+**知识 ID**: knowledge-20260411-155313-comfyinpaint  
+**类型**: strategy  
+**评分**: ⭐⭐⭐⭐⭐ (5/5)  
+**来源**: ComfyUI 社区/YouTube 教程  
+**领域**: AI 图像生成  
+**工作流类型**: 画中画分阶段生成  
+**核心工具**: ControlNet, IP-Adapter, Inpainting, Z-Image-Turbo
+
+---
+
+## 核心设计理念
+
+### 分阶段生成 + 双控架构
+
+**分阶段流程**:
+```
+背景生成 → 画中画区域生成 → 风格统一 → 细节优化
+```
+
+**双控架构**:
+- **ControlNet**: 锁结构(几何/深度/姿态控制)
+- **IP-Adapter**: 注入风格(风格/纹理/色彩迁移)
+
+---
+
+## 工作流总览
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│  阶段 1: 背景/外框生成                                        │
+│  输入:背景提示词 + 参考图 → ControlNet 控制 → 生成背景       │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  阶段 2: 画中画区域生成 (Inpainting)                          │
+│  输入:背景图 + 蒙版 + 画中画提示词 → 局部重绘 → 生成内容     │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  阶段 3: 风格统一 (IP-Adapter)                                │
+│  输入:合成图 + 风格参考图 → IP-Adapter 注入 → 风格融合       │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  阶段 4: 细节优化 (ControlNet + Detailer)                     │
+│  输入:风格化图 → ControlNet 微调 → FaceDetailer → 最终输出  │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 阶段 1:背景/外框生成
+
+### 节点配置
+
+| 节点类型 | 节点名称 | 参数设置 |
+|----------|----------|----------|
+| Checkpoint | SDXL/Flux 主模型 | `albedobaseXL_v21.safetensors` 或 `flux1-dev.safetensors` |
+| ControlNet | Apply Advanced ControlNet | 权重 0.6-0.75 |
+| ControlNet 模型 | ControlNetLoader | `control_sdxl_canny.safetensors` 或 `depth` |
+| 预处理器 | AIO Aux Preprocessor | Canny/Depth/MLSD(根据场景选择) |
+| CLIP Text Encode | 背景提示词 | 描述整体场景氛围 |
+| KSampler | 采样器 | 20-30 步,CFG 7-8,DPM++ 2M Karras |
+
+### 操作步骤
+
+1. **加载背景参考图**(可选)
+   - 使用 `Load Image` 节点加载参考图
+   - 通过 `AIO Aux Preprocessor` 提取结构信息
+
+2. **设置 ControlNet 控制**
+   ```
+   参考图 → 预处理器 → ControlNet 模型 → Apply Advanced ControlNet
+   ```
+   - 控制权重:`0.6-0.75`
+   - 控制模式:`Balanced` 或 `ControlNet is more important`
+   - Starting/Ending Step:`0.0-1.0`(全程控制)
+
+3. **编写背景提示词**
+   ```
+   A cozy living room with large empty wall frame, warm lighting, 
+   modern minimalist interior, soft shadows, high quality photography
+   ```
+
+4. **生成背景图**
+   - 分辨率:`1024x1024` 或 `1280x720`(根据最终输出设定)
+   - 批次数:1(单张背景)
+
+---
+
+## 阶段 2:画中画区域生成(Inpainting)
+
+### 三种实现方案对比
+
+| 方案 | 核心节点 | 适用场景 | 优点 | 缺点 |
+|------|----------|----------|------|------|
+| **A: 原生节点法** | Inpaint Model Conditioning | 理解底层逻辑 | 学习价值高 | 连线复杂 |
+| **B: KJ Nodes 简化版** ⭐ | KJ Nodes: Inpaint Node | 生产环境 | 工作流简洁 | 需安装自定义节点 |
+| **C: SAM 3 智能重绘** | SAM 3 + Grounding DINO | 自动化场景 | 无需手动蒙版 | 依赖 AI 识别准确度 |
+
+### 方案 A:原生节点法
+
+**节点连接**:
+```
+背景图 → VAE Encode → Inpaint Model Conditioning → KSampler → VAE Decode
+蒙版图 ↗
+```
+
+**关键参数**:
+- `denoise`: `0.6-0.8`(重绘强度)
+- `mask_expand`: `10-20`(蒙版扩展像素)
+- 模糊蒙版边缘:让重绘更融合
+
+### 方案 B:KJ Nodes 简化版(推荐)
+
+**核心节点**:
+- `KJ Nodes: Inpaint Node`: 简化重绘流程
+- `KJ Nodes: Mask Editor`: 可视化蒙版编辑
+
+**优势**:工作流连线简洁,适合生产环境
+
+### 方案 C:SAM 3 智能重绘
+
+**核心节点**:
+- `SAM 3 (Segment Anything)`: 自动识别目标区域
+- `Grounding DINO`: 文本引导分割
+
+**操作步骤**:
+1. 输入文本指令:`"the empty frame on the wall"`
+2. SAM 3 自动识别并生成蒙版
+3. 自动执行局部重绘
+
+### 关键参数设置
+
+| 参数 | 推荐值 | 说明 |
+|------|--------|------|
+| denoise | 0.65-0.75 | 平衡原图保留与重绘效果 |
+| mask_expand | 15-25 | 确保边缘融合自然 |
+| CFG | 7-8 | 避免过拟合 |
+| Steps | 25-30 | 保证细节质量 |
+
+### 画中画提示词示例
+
+```
+A beautiful landscape painting inside the frame, 
+mountain lake at sunset, golden hour lighting, 
+oil painting style, detailed brushstrokes, masterpiece
+```
+
+---
+
+## 阶段 3:风格统一(IP-Adapter)
+
+### 核心原理
+
+> **IP-Adapter 负责风格注入,ControlNet 负责结构保持**
+
+⚠️ **重要**: IP-Adapter 单独使用易结构崩坏、手部细节失控,必须配合 ControlNet 使用
+
+### 节点配置
+
+| 节点类型 | 节点名称 | 参数设置 |
+|----------|----------|----------|
+| IPAdapter | IPAdapter Plus | `PLUS(high strength)` 预设 |
+| CLIP Vision | CLIP-ViT-H-14-laion2B-s32B | 风格编码模型 |
+| IPAdapter 权重 | 权重值 | `0.6-0.85` |
+| 合并嵌入组 | concat / average | `concat`(冲突感)或 `norm average`(柔和) |
+| 权重类型 | style transfer | 风格迁移模式 |
+
+### 工作流连接
+
+```
+风格参考图 → IPAdapter Encode → IPAdapter Apply → KSampler
+合成图 → VAE Encode ↗
+```
+
+### 多风格融合
+
+```
+风格图 A → IPAdapter Encode (权重 1.20) ┐
+风格图 B → IPAdapter Encode (权重 1.00) → 合并嵌入组 (concat) → Apply
+```
+
+### 关键参数推荐
+
+| 场景 | IPAdapter 权重 | 合并方式 | 权重类型 |
+|------|---------------|----------|----------|
+| 轻微风格渗透 | 0.4-0.6 | average | style transfer |
+| 平衡融合 | 0.65-0.75 | norm average | style transfer |
+| 强烈风格化 | 0.8-0.85 | concat | style transfer |
+| 多风格融合 | 1.0-1.2 (主) + 0.8 (辅) | concat | style transfer |
+
+### 避坑指南
+
+⚠️ **权重过高** (> 0.85) 易导致画面紊乱  
+✅ **解决方案**: 使用 `fade` 类型 + 噪波强度 0.6 + 模糊 2
+
+---
+
+## 阶段 4:细节优化
+
+### 4.1 ControlNet 微调
+
+**目的**: 在风格化后保持关键结构
+
+| ControlNet 类型 | 权重 | 适用场景 |
+|----------------|------|----------|
+| Canny | 0.4-0.6 | 保持边缘清晰 |
+| Depth | 0.5-0.7 | 保持空间层次 |
+| Tile | 0.6-0.8 | 细节增强/放大 |
+
+**配置技巧**:
+- 使用较低的权重(0.4-0.6)进行微调
+- Starting Step 设为 `0.3-0.5`(后期介入)
+- 避免过度控制导致风格丢失
+
+### 4.2 FaceDetailer / HandDetailer
+
+**目的**: 修复面部/手部细节
+
+| 节点 | 模型 | 参数 |
+|------|------|------|
+| FaceDetailer | `face_yolov8m.pt` | denoise 0.2-0.5 |
+| HandDetailer | `hand_yolov8s.pt` | denoise 0.3-0.6 |
+| SAM | `sam_vit_b_01ec64.pth` | 精准检测 |
+
+**工作流程**:
+```
+风格化图 → 人脸/手部检测 → 局部放大 → 重绘修复 → 合成输出
+```
+
+### 4.3 高清放大(可选)
+
+| 节点 | 模型 | 用途 |
+|------|------|------|
+| Ultimate SD Upscale | `4x-UltraSharp.pth` | 4 倍高清放大 |
+| Tile ControlNet | `control_v11f1e_sd15_tile` | 放大时保持细节 |
+
+---
+
+## 完整参数汇总表
+
+### 阶段 1:背景生成
+| 参数 | 推荐值 |
+|------|--------|
+| 模型 | SDXL / Flux |
+| ControlNet 权重 | 0.6-0.75 |
+| Steps | 20-25 |
+| CFG | 7-8 |
+| 分辨率 | 1024x1024 |
+
+### 阶段 2:Inpainting
+| 参数 | 推荐值 |
+|------|--------|
+| denoise | 0.65-0.75 |
+| mask_expand | 15-25 |
+| Steps | 25-30 |
+| CFG | 7-8 |
+
+### 阶段 3:IP-Adapter
+| 参数 | 推荐值 |
+|------|--------|
+| 权重 | 0.65-0.80 |
+| 预设 | PLUS(high strength) |
+| CLIP Vision | CLIP-ViT-H-14 |
+| 合并方式 | norm average |
+
+### 阶段 4:细节优化
+| 参数 | 推荐值 |
+|------|--------|
+| ControlNet 微调权重 | 0.4-0.6 |
+| FaceDetailer denoise | 0.2-0.5 |
+| Upscale 倍数 | 2x-4x |
+
+---
+
+## 必备自定义节点清单
+
+通过 **ComfyUI Manager** 安装:
+
+```bash
+# ControlNet 相关
+- ComfyUI's ControlNet Auxiliary Preprocessors
+- ComfyUI-Advanced-ControlNet
+
+# IPAdapter 相关
+- IPAdapter Plus (comfyui_ipadapter_plus)
+
+# 简化节点
+- KJ Nodes (comfyui-kjnodes)
+- Efficiency Nodes
+
+# 细节修复
+- Impact Pack (comfyui_essentials)
+- FaceDetailer
+
+# 智能分割
+- SAM 3 (Segment Anything)
+- Grounding DINO
+
+# 其他工具
+- CR Seamless Checker(无缝检查)
+- TextureViewer(3D 预览)
+```
+
+---
+
+## 实战案例:客厅挂画生成
+
+### 完整提示词
+
+**阶段 1 - 背景**:
+```
+A modern living room with a large empty wooden frame on the wall, 
+warm afternoon sunlight, minimalist interior design, 
+soft shadows, cozy atmosphere, interior photography, 8k --ar 16:9
+```
+
+**阶段 2 - 画中画**:
+```
+A serene mountain lake landscape at golden hour, 
+reflection in water, dramatic sky, oil painting style, 
+visible brushstrokes, impasto technique, masterpiece
+```
+
+**阶段 3 - 风格参考**:
+- 上传 1-2 张目标风格的油画作品作为 IP-Adapter 参考
+
+### 工作流执行顺序
+
+1. **生成背景**: 使用 Canny ControlNet 控制房间结构
+2. **绘制蒙版**: 在空画框区域绘制蒙版(或使用 SAM 3 自动识别)
+3. **局部重绘**: denoise 0.7,生成画中画内容
+4. **风格注入**: IP-Adapter 权重 0.75,融合油画风格
+5. **细节修复**: FaceDetailer 修复可能的人物(如有)
+6. **高清输出**: Ultimate SD Upscale 放大至 4K
+
+---
+
+## 常见问题与解决方案
+
+| 问题 | 原因 | 解决方案 |
+|------|------|----------|
+| 画中画边缘不融合 | 蒙版扩展不足 | 增加 `mask_expand` 至 20-30 |
+| 风格与背景不协调 | IP-Adapter 权重过高 | 降至 0.6-0.7,使用 `norm average` |
+| 画中画结构崩坏 | 缺少 ControlNet 控制 | 添加 Canny/Depth ControlNet(权重 0.5) |
+| 面部/手部畸形 | 细节质量不足 | 添加 FaceDetailer/HandDetailer |
+| 整体风格不统一 | 分阶段风格差异大 | 阶段 1 也使用相同 IP-Adapter 参考 |
+| 生成速度慢 | 多阶段串行执行 | 使用批处理,低分辨率测试后再高清生成 |
+
+---
+
+## 进阶技巧
+
+### 1. 多画中画嵌套
+```
+背景 → 画中画 A → 画中画 B(嵌套在 A 内)
+```
+- 使用多个 Inpainting 节点串联
+- 每个阶段使用独立蒙版
+- IP-Adapter 可分别控制不同区域风格
+
+### 2. 动态蒙版生成
+```
+Grounding DINO + SAM 3 → 自动蒙版 → Inpainting
+```
+- 文本指令:`"the frame on the wall"`
+- 全自动识别和重绘
+
+### 3. 风格渐变融合
+```
+IPAdapter A (权重 0.8) + IPAdapter B (权重 0.4) → 渐变融合
+```
+- 使用 `average` 合并方式
+- 调整权重比例控制风格倾向
+
+### 4. 批量生成变体
+```
+同一背景 + 不同画中画提示词 → 批量输出
+```
+- 使用 `Batch Prompt Schedule` 节点
+- 一次生成多个画中画变体
+
+---
+
+## 关联知识
+
+- **Midjourney v7 厚涂风格**: 可使用 MJ 生成风格参考图,再通过本工作流在 ComfyUI 中精确复现
+- **Nano Banana 多图融合**: 可将多图融合结果作为本工作流的输入
+- **ControlNet+IPAdapter 双控架构**: 本工作流的核心技术基础
+
+---
+
+## 更新日志
+
+- 2026-04-11: 初始入库,包含 4 阶段完整工作流、3 种 Inpainting 方案对比、实战案例

+ 165 - 0
knowledge/Midjourney_v7_Impasto_厚涂风格参数配置.md

@@ -0,0 +1,165 @@
+# Midjourney v7 Impasto 厚涂风格参数配置
+
+**知识 ID**: knowledge-20260411-155313-mj7impasto  
+**类型**: tool  
+**评分**: ⭐⭐⭐⭐⭐ (5/5)  
+**来源**: 小红书/Midjourney 官方文档  
+**领域**: AI 图像生成  
+**工具**: Midjourney v7  
+**风格**: Impasto 厚涂
+
+---
+
+## 核心发现
+
+Midjourney v7 在美学质量与笔触质感方面表现优异,特定 sref 代码可生成几乎与真实绘画无法区分的厚涂技法效果。**v8 版本灵活性降低,艺术风格探索建议继续使用 v7**。
+
+### v7 vs v8 厚涂风格对比
+
+| 维度 | v7 | v8 |
+|------|-----|-----|
+| 笔触质感 | ⭐⭐⭐⭐⭐ 自然流畅 | ⭐⭐⭐⭐ 稍显僵硬 |
+| 风格灵活性 | ⭐⭐⭐⭐⭐ 高 | ⭐⭐⭐ 降低 |
+| 厚涂效果 | ⭐⭐⭐⭐⭐ 无敌 | ⭐⭐⭐⭐ 良好 |
+
+---
+
+## 核心参数详解
+
+### 1. `--sref` 风格参考代码
+
+**基础语法**:
+```bash
+/imagine prompt: [描述] --sref [风格代码] --v 7
+```
+
+**多风格融合**(最多 5 个):
+```bash
+/imagine prompt: [描述] --sref 代码 1 代码 2 代码 3 --v 7
+```
+
+**常用厚涂风格代码**:
+- **新派写实油画**: `sref 738792225` ⭐ 推荐
+- 获取方法:
+  1. 在 Discord 使用 `/describe` 分析喜欢的厚涂作品
+  2. 使用 `/style-save` 保存生成的风格
+  3. 从社区共享代码库获取(Reddit、PromptHero)
+
+### 2. `--sw` 风格权重参数
+
+**语法**:`--sw [0.1-2.0]`
+
+**权重范围与效果**:
+
+| 权重值 | 效果强度 | 适用场景 |
+|--------|----------|----------|
+| 0.1-0.3 | 轻微风格渗透 | 保留原风格基础上微调 |
+| 0.4-0.7 | 平衡融合 | 推荐默认值,风格与内容均衡 |
+| 0.8-1.2 | 强烈风格化 | 明显厚涂笔触质感 |
+| 1.3-2.0 | 极致风格 | 完全主导,可能牺牲细节 |
+
+**厚涂风格推荐设置**: `--sw 1.0-1.5`
+
+### 3. `--v 7` 版本锁定
+
+⚠️ **必须显式指定** `--v 7`,v8 版本灵活性降低不适合厚涂风格
+
+---
+
+## 完整提示词公式
+
+### 厚涂风格三段式公式
+
+```
+[主体画面描述] + [厚涂笔触/材质描述] + [光影/色彩氛围] --sref [代码] --sw [权重] --v 7 --ar [比例] --stylize [值]
+```
+
+### 厚涂专用关键词库
+
+| 关键词 | 中文含义 | 效果 |
+|--------|----------|------|
+| `thick impasto brushwork` | 厚涂笔触 | 增强笔触质感 |
+| `heavy paint texture` | 厚重颜料肌理 | 增加物理厚度 |
+| `visible brushstrokes` | 可见笔触 | 拒绝平滑感 |
+| `palette knife texture` | 调色刀纹理 | 特殊肌理效果 |
+| `impasto technique` | 厚涂技法 | 整体风格定义 |
+
+---
+
+## 实战示例
+
+### 示例 1:新派写实油画
+```bash
+{你的主体} + The painting is in a realistic oil painting style, with clearly visible brushstrokes, vibrant colors, and strong light. --chaos 30 --exp 85 --sref 738792225 --profile qvu9plm --v 7.0 --ar 2:3
+```
+
+### 示例 2:人物厚涂肖像
+```bash
+A warrior princess with flowing red hair, thick impasto oil painting style, visible brushstrokes, palette knife texture, dramatic chiaroscuro lighting, warm golden hour glow --sref 1847293 --sw 1.3 --v 7 --ar 2:3 --stylize 750
+```
+
+### 示例 3:风景厚涂
+```bash
+Mountain landscape at sunset, heavy impasto technique, thick layers of oil paint, textured canvas, bold brushwork, vibrant color palette, atmospheric perspective --sref 2938471 --sw 1.5 --v 7 --ar 16:9 --stylize 800
+```
+
+---
+
+## 参数组合推荐表
+
+| 场景 | --sref | --sw | --stylize | --q | --ar |
+|------|--------|------|-----------|-----|------|
+| 人物厚涂肖像 | 1-2 个代码 | 1.2-1.5 | 700-850 | 2 | 2:3 |
+| 风景厚涂 | 2-3 个代码 | 1.3-1.8 | 750-900 | 2 | 16:9 |
+| 静物厚涂 | 1 个代码 | 1.0-1.3 | 600-750 | 2 | 4:5 |
+| 抽象厚涂 | 2-3 个代码 | 1.5-2.0 | 850-1000 | 4 | 1:1 |
+| 快速测试 | 1 个代码 | 1.0 | 500 | 0.5 | 1:1 |
+
+---
+
+## 避坑指南
+
+| 问题 | 原因 | 解决方案 |
+|------|------|----------|
+| 厚涂效果不明显 | --sw 权重太低 | 提升至 1.3-1.8 |
+| 笔触过于粗糙 | --sw 权重过高 | 降至 0.8-1.2 |
+| 风格冲突 | 多代码不兼容 | 减少至 1-2 个代码 |
+| 细节丢失 | --stylize 过高 | 降至 600-750 |
+| 生成速度慢 | --q 4 + 多代码 | 先用 --q 0.5 测试 |
+| v8 效果不如 v7 | 版本灵活性差异 | 强制使用 --v 7 |
+
+---
+
+## 拒绝塑料感 4 招
+
+### 1. 拒绝平滑,增加物理厚度
+使用 `Thick impasto brushwork` 或 `Heavy paint texture` 关键词
+
+### 2. 治愈系万能公式
+```
+Deep blue twilight ambient + Warm orange window light
+```
+90% 冷调 + 10% 暖色,营造层次感
+
+### 3. 让水活过来
+使用 `Swirling water patterns` + `Churning white water`
+
+### 4. 参数降噪,返璞归真
+```bash
+--s 200 搭配 --style raw
+```
+降低 AI 过度修饰,回归自然质感
+
+---
+
+## 关联知识
+
+- **ComfyUI 厚涂复现**: 使用 IPAdapter + ControlNet 双控架构在 ComfyUI 中复现 MJ 厚涂风格
+- **风格代码获取**: `/describe` + `/style-save` 工作流
+- **v7 vs v8 对比**: v7 适合艺术风格探索,v8 适合精确解剖结构
+
+---
+
+## 更新日志
+
+- 2026-04-11: 初始入库,包含具体风格代码 738792225、--sw 参数详解、厚涂关键词库

+ 261 - 0
knowledge/Nano_Banana_多图融合_ComfyUI 工作流.md

@@ -0,0 +1,261 @@
+# Nano Banana 多图融合 ComfyUI 工作流
+
+**知识 ID**: knowledge-20260411-155313-nanobanana  
+**类型**: strategy  
+**评分**: ⭐⭐⭐⭐⭐ (5/5)  
+**来源**: 小红书/ComfyUI 社区  
+**领域**: AI 图像生成  
+**工作流类型**: 多图融合  
+**核心工具**: Nano Banana, Google Gemini Image, ComfyUI
+
+---
+
+## 核心能力
+
+| 能力指标 | 规格 |
+|----------|------|
+| 最多参考图数量 | **14 张**(实际 10 张可达 100% 还原) |
+| 角色一致性 | 单工作流最多 **5 个角色** + **14 个物体** |
+| 面部一致性 | **90-95%** |
+| 中文渲染 | 准确率 **90%+** |
+| 支持宽高比 | 包括极端比例 1:8、8:1、4:1、1:4 |
+| 产品一致性 | 标签文字不崩坏 |
+
+---
+
+## ComfyUI 工作流配置
+
+### 方法 1:单图编辑
+
+**工作流节点连接**:
+```
+Load Image → Image Scale → Google Gemini Image → LibLib Translate → Save Image
+```
+
+**参数设置**:
+- 图像缩放:`1536×1536`
+- 模型:`gemini-2.5-flash-image-preview`
+- 提示词:**必须包含** `"把这*张图组合成一张完整的新图像"`
+
+### 方法 2:双图编辑(推荐,效果更稳定)
+
+**工作流节点连接**:
+```
+Load Image (产品图) → Image Scale ┐
+Load Image (场景图) → Image Scale → Google Gemini Image → LibLib Translate → Save Image
+```
+
+**提示词示例**:
+```
+"把这两张图组合成一张完整的新图像,将产品自然融入场景中,保持产品标签文字清晰"
+```
+
+### 方法 3:三图编辑
+
+**工作流节点连接**:
+```
+Load Image (场景图) → Image Scale ┐
+Load Image (产品图) → Image Scale ├→ Google Gemini Image → LibLib Translate → Save Image
+Load Image (元素图) → Image Scale ┘
+```
+
+**提示词示例**:
+```
+"把这三张图组合成一张完整的新图像,将产品和杯子自然融入家居场景中,保持产品标签文字清晰"
+```
+
+---
+
+## 15 种多图融合场景
+
+| 序号 | 场景 | 描述 |
+|------|------|------|
+| 1 | 杂志封面 | 融合多张参考图生成专业封面 |
+| 2 | 漫画分镜 | 保持角色一致性的多格漫画 |
+| 3 | 信息图 | 自动搜索数据生成带图表的信息图 |
+| 4 | 情绪九宫格 | 同一人物 9 种表情 |
+| 5 | 极限运动场景 | 同一人物在不同运动场景 |
+| 6 | 产品对比海报 | 多产品对比展示 |
+| 7 | 科研绘图 | 顶刊级科研配图 |
+| 8 | 电影感人像 | 带详细相机参数的专业人像 |
+| 9 | 艺术风格转换 | 多风格同一人物 |
+| 10 | 背景替换 | 保持人物换背景 |
+| 11 | 服装替换 | 保持人物换服装 |
+| 12 | 创意场景转换 | 动作人偶风格等 |
+| 13 | 多语言信息图 | 一键翻译排版 |
+| 14 | 四方连续贴图 | 无缝纹理生成 |
+| 15 | 虚拟试衣 | 服装上身效果 |
+
+---
+
+## 实测心得
+
+### 关键发现
+
+1. **提示词决定出图效果** ⭐⭐⭐⭐⭐
+   - **必须写**: `"把这*张图组合成一张完整的新图像"`
+   - 缺少此句会导致生成效果不稳定
+
+2. **双图编辑效果更稳定** ⭐⭐⭐⭐
+   - 单图编辑:适合简单修改
+   - 双图编辑:推荐默认方案
+   - 三图以上:复杂度增加,需更具体提示词
+
+3. **产品一致性能力出色** ⭐⭐⭐⭐⭐
+   - 不必担心产品标签文字崩坏
+   - 适合电商产品场景图生成
+
+4. **清晰度优化**
+   - 如生成图清晰度不够,可使用高清放大工作流
+   - 推荐:Ultimate SD Upscale + Tile ControlNet
+
+---
+
+## 成本说明
+
+### Nano Banana 定价
+
+| 版本 | 分辨率 | 价格 | 备注 |
+|------|--------|------|------|
+| Nano Banana 2 | 1K | $0.067/张 | 性价比最高 |
+| Nano Banana 2 | 2K | $0.101/张 | |
+| Nano Banana 2 | 4K | $0.151/张 | |
+| Nano Banana Pro | 1K | $0.134/张 | 比 Flash 贵 50% |
+
+### 不同平台成本对比
+
+| 平台 | 成本范围 | 建议 |
+|------|----------|------|
+| 官方 API | $0.067-0.151/张 | 稳定可靠 |
+| 第三方平台 A | $0.02-0.08/张 | 价格低,注意稳定性 |
+| 第三方平台 B | $0.10-0.15/张 | 价格高,可能有额外功能 |
+
+⚠️ **注意**: 不同平台调用 Banana 的成本不同(从 $0.02~$0.15 不等),需根据需求选择
+
+---
+
+## 避坑指南
+
+| 问题 | 说明 | 解决方案 |
+|------|------|----------|
+| SynthID 水印 | 不可移除 | 接受或后期处理 |
+| Thinking 模式 | 强制启用 | 无法关闭,等待完成 |
+| 提示词不具体 | 效果不稳定 | 极度具体描述需求 |
+| 直接使用 Pro | 成本高 | 先用 Flash 测试提示词 |
+| 单次生成 | 成本高 | 使用 Batch API 节省 50% |
+
+### 推荐工作流
+
+```
+1. Flash 测试提示词 → 2. 优化提示词 → 3. Pro 批量生成 → 4. 高清放大
+```
+
+---
+
+## 实战案例
+
+### 案例 1:电商产品场景图
+
+**输入**:
+- 产品图:投影仪正面图
+- 场景图:雪山湖畔风景
+
+**提示词**:
+```
+"把这两张图组合成一张完整的新图像,将投影仪自然融入雪山湖畔场景中,保持产品外观和标签清晰"
+```
+
+**输出**:电商宣传场景图
+
+**耗时**:5-6 分钟  
+**成本**:$0.067 × 2(测试 + 正式)= $0.134
+
+---
+
+### 案例 2:中餐厅菜品图自动化
+
+**工作流**:Nanobanana + ComfyUI
+
+**流程**:
+```
+厨房出餐摆盘 → 手机随手拍 → 5-6 分钟生成专业菜单图
+```
+
+**支持**:
+- 横版/竖版可变尺寸
+- 批量生成
+- 保持菜品色泽和质感
+
+**应用场景**:
+- 餐厅菜单
+- 外卖平台图片
+- 社交媒体推广
+
+---
+
+### 案例 3:小说转短剧分镜
+
+**流程**:
+```
+1. 去 AO3 找短文
+2. 和 Gemini 说将其生成 12 张图
+3. 得到人物一致性非常高的一组图
+4. 配合图片转视频工具制作小说转短剧
+```
+
+**成本**:极低($0.067 × 12 = $0.804)
+
+**优势**:
+- 人物一致性高(90-95%)
+- 快速生成分镜
+- 可批量制作
+
+---
+
+## 与 ComfyUI 工作流整合
+
+### 整合方案
+
+```
+Nano Banana 多图融合 → ComfyUI 后处理
+                        ├─ ControlNet 结构微调
+                        ├─ IP-Adapter 风格统一
+                        ├─ FaceDetailer 细节修复
+                        └─ Upscale 高清放大
+```
+
+### 典型应用场景
+
+1. **电商产品图**:
+   - Nano Banana: 产品 + 场景融合
+   - ComfyUI: 细节优化、高清放大
+
+2. **角色一致性漫画**:
+   - Nano Banana: 多格分镜生成
+   - ComfyUI: 风格统一、对话框添加
+
+3. **科研绘图**:
+   - Nano Banana: 图表 + 数据融合
+   - ComfyUI: 标注优化、格式调整
+
+---
+
+## 关联知识
+
+- **ComfyUI 画中画工作流**: 可将 Nano Banana 生成结果作为输入进行进一步处理
+- **Midjourney v7 厚涂风格**: 可使用 MJ 生成风格参考图,通过 Nano Banana 融合到目标场景
+- **ControlNet+IPAdapter 双控架构**: ComfyUI 后处理的核心技术
+
+---
+
+## 官方资源
+
+- **API 文档**: https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-image-preview
+- **DeepMind 模型页**: https://deepmind.google/models/gemini-image/flash/
+- **AI Studio**: https://aistudio.google.com/models/gemini-3-1-flash-image
+
+---
+
+## 更新日志
+
+- 2026-04-11: 初始入库,包含 ComfyUI 工作流配置、3 种编辑方法、15 种场景、3 个实战案例

+ 20 - 0
knowledge/knowledge-20260412-005015-nano-banana-image-urls.json

@@ -0,0 +1,20 @@
+{
+  "id": "knowledge-20260412-005015-nano-banana-image-urls",
+  "task": "在图像生成任务中使用 nano_banana 工具传递参考图",
+  "content": "当使用 nano_banana 工具进行图像生成时,image_urls 参数不支持本地相对路径,必须使用 HTTP/HTTPS CDN URL。\n\n## 错误做法\n\n**直接传本地相对路径**:\n```json\n{\n  \"image_urls\": [\"examples/production_restore/features/character_asset/character_ref_kneel.png\"]\n}\n```\n返回错误:HTTP 503: \"images[].data 看起来是文件路径但文件不存在\"\n\n**另一个错误示例**:\n```json\n{\"image_urls\": [\"examples/production_restore/img_1.png\"]}\n```\n报错:HTTP 503: images[].data 看起来是文件路径但文件不存在\n\n**注意**:虽然角色说明中提到 toolhub.py 内置的 `_preprocess_params` 函数会自动将本地路径上传到 OSS,但实际测试发现这个功能没有生效,需要手动调用 image_uploader 上传。\n\n## 正确做法\n\n1. 先调用 image_uploader 上传本地图片:\n```python\n# 步骤 1:上传图片\ncdn_url = image_uploader(local_path=\"examples/production_restore/img_1.png\")\n# 返回:https://res.cybertogether.net/toolhub_images/img_1.png\n\n# 步骤 2:调用 nano_banana\ntoolhub_call(tool_id=\"nano_banana\", params={\n    \"image_urls\": [cdn_url],\n    \"prompt\": \"...\"\n})\n```\n\n2. 再将 CDN URL 传入 nano_banana:\n```python\ntoolhub_call(tool_id=\"nano_banana\", params={\n  \"image_urls\": [\"https://res.cybertogether.net/toolhub_images/character_ref_kneel.png\"]\n})\n```\n\n## 原因\n\nnano_banana 工具运行在远程环境中,无法直接访问本地文件系统,必须通过 HTTP URL 访问图片。\n\n## 案例\n\nimg_1 生成任务中,最初传入相对路径导致 HTTP 503 错误,错误信息明确指出「请传 HTTP URL 或绝对路径,调用方应先把本地文件上传到 OSS 再传 CDN URL」。改用 image_uploader 上传三张参考图后获得 CDN URL,再次调用 nano_banana 成功生成图像。",
+  "types": ["experience"],
+  "tags": {
+    "intent": "图像生成",
+    "state": "nano_banana",
+    "tool": "image_uploader"
+  },
+  "score": 5,
+  "source": {
+    "category": "research"
+  },
+  "created_at": "2026-04-12T00:50:15Z",
+  "updated_at": "2026-04-12T01:53:12Z",
+  "tool_ids": ["tools/image_gen/nano_banana", "tools/upload/image_uploader"],
+  "capability_ids": ["CAP-003"],
+  "related_knowledge_ids": ["knowledge-20260409-182612-108d", "knowledge-20260412-010725-nano-banana-http503"]
+}

+ 20 - 0
knowledge/knowledge-20260412-010725-nano-banana-http503.json

@@ -0,0 +1,20 @@
+{
+  "id": "knowledge-20260412-010725-nano-banana-http503",
+  "task": "nano_banana 工具图片传递方式:需先上传获取 CDN URL",
+  "content": "当使用 nano_banana 工具进行图像生成时,如果传入本地文件路径会报错 HTTP 503:\"images[].data 看起来是文件路径但文件不存在\"。\n\n**错误做法**:直接传本地相对路径\n```json\n{\n  \"image_urls\": [\"examples/production_restore/features/character_asset/character_ref_kneel.png\"]\n}\n```\n\n**正确做法**:\n1. 先调用 image_uploader 上传本地图片:\n```python\nimage_uploader(local_path=\"examples/production_restore/features/character_asset/character_ref_kneel.png\")\n→ 返回 cdn_url: \"https://res.cybertogether.net/toolhub_images/character_ref_kneel.png\"\n```\n2. 再将 CDN URL 传入 nano_banana:\n```python\ntoolhub_call(tool_id=\"nano_banana\", params={\n  \"image_urls\": [\"https://res.cybertogether.net/toolhub_images/character_ref_kneel.png\"]\n})\n```\n\n**原因**:nano_banana 工具运行在远程环境中,无法直接访问本地文件系统,必须通过 HTTP URL 访问图片。\n\n**案例**:\n- img_1 生成:传入相对路径导致 HTTP 503 错误,改用 image_uploader 上传后成功\n- img_4 生成:最初直接传本地路径 \"examples/production_restore/features/character_asset/character_ref_side.png\" 导致失败,改用 image_uploader 上传 4 张图片获取 CDN URL 后成功生成",
+  "types": ["experience"],
+  "tags": {
+    "intent": "图像生成",
+    "state": "nano_banana",
+    "tool": "image_uploader"
+  },
+  "score": 4,
+  "source": {
+    "category": "research"
+  },
+  "created_at": "2026-04-12T01:07:25Z",
+  "updated_at": "2026-04-12T01:53:12Z",
+  "tool_ids": ["tools/image_gen/nano_banana", "tools/upload/image_uploader"],
+  "capability_ids": ["CAP-001", "CAP-011"],
+  "related_knowledge_ids": ["knowledge-20260409-182612-108d", "knowledge-20260412-005015-nano-banana-image-urls", "knowledge-20260412-010726-nano-banana-multi-fusion"]
+}

Разлика између датотеке није приказан због своје велике величине
+ 3 - 0
knowledge/knowledge-20260412-010726-nano-banana-multi-fusion.json


+ 20 - 0
knowledge/knowledge-20260412-014259-3abf.json

@@ -0,0 +1,20 @@
+{
+  "id": "knowledge-20260412-014259-3abf",
+  "task": "在 nano_banana 图生图任务中,参考图需要先上传获取 CDN URL 再传入",
+  "content": "当使用 nano_banana 工具进行多图融合生图时,image_urls 参数不能直接传本地文件路径(如 examples/xxx.png),否则会报错「文件不存在」。正确做法是:先调用 image_uploader 工具将本地图片上传到 OSS 获取 CDN URL,再将 CDN URL 传入 nano_banana 的 image_urls 参数。案例:本次 img_1 生成任务中,先调用 image_uploader 上传 character_ref_img1.png、background_green_img1.png、palette_impasto_img1_v2.png、easel_blank_canvas_img4.png 四张参考图,获取 CDN URL 后传入 nano_banana,生成成功。",
+  "types": ["experience"],
+  "tags": {
+    "intent": "图像生成",
+    "state": "nano_banana",
+    "tool": "image_uploader"
+  },
+  "score": 4,
+  "source": {
+    "category": "research"
+  },
+  "created_time": 1776067979,
+  "tool_ids": ["tools/image_gen/nano_banana", "tools/upload/image_uploader"],
+  "capability_ids": [],
+  "requirement_ids": [],
+  "resource_ids": []
+}

+ 21 - 0
knowledge/strategy/品类分组判断标准.json

@@ -0,0 +1,21 @@
+{
+  "id": "knowledge-20260410-category-grouping",
+  "task": "工作流品类分组的判断标准",
+  "content": "在分析多条工作流进行品类分组时,应基于以下维度判断:\n\n1. **核心主体**:工作流围绕什么核心对象展开(人物/场景/光影/纹理)\n\n2. **技术手段相似性**:是否使用相似的工具链和技术方法\n\n3. **输出目标一致性**:最终产出的内容类型是否相同\n\n4. **粒度把握**:品类名称应品类无关(如'人物肖像'而非'电商人物肖像'),便于跨领域比较\n\n本次分析案例:\n- wf_002(局部特写)+ wf_003(近景肖像)→ 人物肖像类(都是人物主体,都涉及细节修复和身份锁定)\n- wf_001 → 场景构建类(场景 + 人物综合,涉及 3D 建模和场景合成)\n- wf_004 → 光影艺术类(专门的光影控制,使用 ControlNet/IC-Light 等)\n- wf_005 → 纹理风格类(材质纹理效果,涉及颗粒/纸张/复古风格)",
+  "types": ["strategy"],
+  "tags": {
+    "domain": "工作流分析",
+    "method": "品类分组",
+    "key_skill": "语义聚类"
+  },
+  "score": 4,
+  "source": {
+    "category": "execution"
+  },
+  "created_at": "2026-04-10T16:58:05",
+  "status": "active",
+  "related_knowledge": [
+    "knowledge-20260410-variant-analysis-method",
+    "knowledge-20260410-causal-reasoning"
+  ]
+}

+ 20 - 0
knowledge/strategy/粗工序因果推理方法.json

@@ -0,0 +1,20 @@
+{
+  "id": "knowledge-20260410-causal-reasoning",
+  "task": "工作流粗工序提取的因果推理方法",
+  "content": "在总结粗工序时,必须解释'为什么先做 A 再做 B',而不是简单列举频次。核心推理框架包括:\n\n1. **依赖关系原则**:后续步骤依赖前序步骤的输出(如'局部细节修复必须在身份锁定之后进行,否则修复结果无法保持角色一致性')\n\n2. **先整体后局部原则**:先建立基础框架再处理细节(如'首先生成基础人像,因为后续所有细节处理都依赖于一个可用的基础图像')\n\n3. **先生成后修复原则**:先产生可用结果再进行优化(如'放大和精修是最终输出前的必要步骤,放在后期处理')\n\n4. **分离 - 合成策略**:将独立元素分别处理后再合并(如'场景背景和人物图像分别生成后再进行合成,这种分离策略允许对场景和人物独立优化,避免相互干扰')\n\n5. **先规划后执行原则**:先设计蓝图再技术实现(如'整体光照方案是所有后续技术实现的基础蓝图,必须在布局确定后进行配置')\n\n6. **先基础后风格化原则**:先完成基础处理再进行艺术增强(如'色彩调整是光影/纹理处理完成后的最终润色步骤,过早调色会干扰基础效果的准确呈现')",
+  "types": ["strategy"],
+  "tags": {
+    "domain": "工作流分析",
+    "method": "粗工序提取",
+    "key_skill": "因果推理"
+  },
+  "score": 5,
+  "source": {
+    "category": "execution"
+  },
+  "created_at": "2026-04-10T16:58:05",
+  "status": "active",
+  "related_knowledge": [
+    "knowledge-20260410-variant-analysis-method"
+  ]
+}

+ 17 - 0
knowledge/strategy/能力模块变体分析方法.json

@@ -0,0 +1,17 @@
+{
+  "id": "knowledge-20260410-variant-analysis-method",
+  "task": "能力模块实现变体分析方法",
+  "content": "在工作流分析中,为能力模块分析实现变体的方法:\n\n1. **变体聚类依据**:根据工具/方法相似性进行聚类,每个模块聚类为 2-4 个变体\n\n2. **变体命名规范**:「工具/方法」+「方案」,如「LoRA 方案」、「IPAdapter 方案」、「ControlNet 方案」、「Photoshop 后期方案」等\n\n3. **变体结构**:\n   - name:变体名称(符合命名规范)\n   - description:变体描述(一句话说明核心方法)\n   - steps:典型执行步骤(2-4 步,动宾结构)\n   - source_workflows:来源工作流列表(如 [\"wf_001\", \"wf_002\"])\n\n4. **分析要点**:\n   - 忠实于原始工作流数据,不臆造不存在的工具或方法\n   - 变体之间应有明显的方法差异(如 AI 生成 vs 手工绘制 vs 外部素材)\n   - 步骤描述应体现因果性编排思路(为什么先做 A 再做 B)\n   - 注明每个变体来自哪些工作流,便于追溯\n\n5. **常见变体类型**:\n   - AI 生成方案(使用 FLUX/ComfyUI/Midjourney 等)\n   - 控制技术方案(ControlNet/LoRA/PuLID 等)\n   - 后期处理方案(Photoshop/Illustrator 等)\n   - 提示词方案(通过提示词工程实现)\n   - 3D/渲染方案(Blender 等)",
+  "types": ["strategy"],
+  "tags": {
+    "domain": "工作流分析",
+    "method": "变体分析",
+    "output_format": "JSON"
+  },
+  "score": 4,
+  "source": {
+    "category": "execution"
+  },
+  "created_at": "2026-04-10T16:54:50",
+  "status": "active"
+}

Разлика између датотеке није приказан због своје велике величине
+ 9 - 0
knowledge_batch_20260409.json


+ 26 - 0
knowledge_batch_20260409_execution.json

@@ -0,0 +1,26 @@
+{
+  "upload_batch": "X 搜索接口稳定性问题",
+  "upload_date": "2026-04-09",
+  "source_type": "execution",
+  "knowledge_count": 1,
+  "knowledge": [
+    {
+      "id": "knowledge-20260409-exec-001",
+      "task": "X 搜索接口稳定性问题及应对策略",
+      "content": "## 问题描述\n在执行 X 渠道调研时,x_search 工具在以下情况出现连续失败:\n- 关键词 1(close-up portrait AI workflow tutorial):成功返回 20 条结果\n- 关键词 2(portrait bokeh background AI workflow):连续 5 次搜索均返回\"X 搜索异常\"或\"搜索失败:未知错误\"\n- 关键词 3(facial expression AI art workflow):连续搜索返回异常\n- 尝试替代关键词(portrait bokeh AI, bokeh background portrait AI):同样失败\n\n## 应对策略\n1. **重试机制**:接口失败时应重试 2 次,若仍失败则标注渠道状态为\"接口失败\"\n2. **降级处理**:当部分关键词失败时,基于已成功获取的数据继续完成调研\n3. **结果标注**:在输出 JSON 中明确标注\"渠道状态\": \"部分接口失败\",并记录实际获取的数据量\n4. **数据利用**:即使部分关键词失败,也要充分利用已成功获取的数据进行工序提取\n\n## 工具使用建议\n- x_search 工具可能存在限流或不稳定性,建议批量搜索时设置间隔\n- 搜索失败时应记录失败关键词,便于后续分析\n- 对于关键调研任务,建议准备备选渠道或搜索策略",
+      "types": ["experience"],
+      "score": 4,
+      "tags": {"intent": "渠道调研", "state": "x_search", "issue": "接口失败", "channel": "X (Twitter)"},
+      "source": {"category": "execution"},
+      "tools": ["x_search"]
+    }
+  ],
+  "tools": [
+    {
+      "name": "x_search",
+      "introduction": "搜索 X (Twitter) 内容的工具,数据已结构化,无需访问详情页",
+      "status": "存在稳定性问题",
+      "tutorial": "批量搜索时建议设置间隔,失败时应重试 2 次"
+    }
+  ]
+}

+ 34 - 0
knowledge_batch_20260410_workflow_analysis.json

@@ -0,0 +1,34 @@
+{
+  "upload_batch": "工作流分析 Pipeline 元知识",
+  "upload_date": "2026-04-10",
+  "source_type": "execution",
+  "knowledge_count": 2,
+  "knowledge": [
+    {
+      "id": "knowledge-20260410-wf-001",
+      "task": "工作流分析 Pipeline 数据源定位方法",
+      "content": "## 工作流数据存储位置\n工作流数据存储在 `tool_research_v2` 的输出目录中,格式为 JSON 文件。\n\n## 数据结构\n```json\n{\n  \"渠道名称\": \"小红书/X (Twitter)/YouTube\",\n  \"工序发现\": [\n    {\n      \"方案名称\": \"...\",\n      \"工序步骤\": [\n        {\n          \"步骤序号\": 1,\n          \"步骤描述\": \"...\",\n          \"使用工具\": \"...\",\n          \"用户输入\": \"...\",\n          \"输出结果\": \"...\"\n        }\n      ],\n      \"帖子链接\": \"...\"\n    }\n  ]\n}\n```\n\n## 工作流分析入口\n**脚本路径**:`examples/workflow_analysis/run.py`\n\n### 功能\n- 从 `tool_research_v2` 输出目录读取 JSON 文件\n- 自动编号为 `wf_001`, `wf_002`, `wf_003`...\n- 输出意图分析结果到 `examples/research/outputs/analysis/`\n\n### 关键代码逻辑\n1. `load_workflows_from_dir()` 扫描输入目录下所有 JSON 文件\n2. 支持两种目录结构:\n   - 单次调研输出:直接含 `*.json` 文件\n   - 批量调研输出:含 `00/`, `01/` 等子目录\n3. 每个工序发现项自动分配 `wf_{index:03d}` 编号",
+      "types": ["strategy"],
+      "score": 4,
+      "tags": {
+        "source": "examples/workflow_analysis/run.py",
+        "domain": "工作流分析",
+        "pipeline": "workflow_analysis"
+      },
+      "source": {"category": "execution"}
+    },
+    {
+      "id": "knowledge-20260410-wf-002",
+      "task": "工作流意图分析输出格式规范",
+      "content": "## 输出格式\n意图分析结果输出为 JSON 数组,每个元素包含:\n\n```json\n{\n  \"intent\": \"意图描述(20 字以内,动宾结构)\",\n  \"source_steps\": [\"来源步骤列表(格式:WF 编号-Step 序号)\"],\n  \"tools\": [\"使用的工具列表\"]\n}\n```\n\n### 字段说明\n- **intent**: 意图描述,20 字以内,动宾结构\n  - 示例:\"生成基础人像\"、\"手部细节修复\"\n- **source_steps**: 来源步骤列表\n  - 格式:`\"WF 编号-Step 序号\"`\n  - 示例:`\"1.1-Step1\"`、`\"6.1-Step2\"`\n- **tools**: 使用的工具列表\n  - 示例:`[\"FLUX.1 Dev\", \"ControlNet Inpaint\", \"InstantID\"]`\n\n## 意图聚类原则\n\n1. **相似步骤合并**:多个工作流的相似步骤合并为同一意图\n   - 示例:多个工作流的\"第一步生成基础图像\" → \"生成基础人像\"\n\n2. **品类无关描述**:意图描述要品类无关,便于跨工作流比较\n\n3. **工具列表合并**:工具列表合并该意图下所有工作流使用的工具\n\n## 输出文件路径\n`examples/research/outputs/analysis/wf_{编号}_intents.json`\n\n### 示例\n- `wf_001_intents.json`\n- `wf_002_intents.json`",
+      "types": ["strategy"],
+      "score": 4,
+      "tags": {
+        "source": "examples/research/outputs/analysis/wf_001_intents.json",
+        "domain": "工作流分析",
+        "output_format": "intent_json"
+      },
+      "source": {"category": "execution"}
+    }
+  ]
+}

+ 51 - 0
knowledge_batch_20260411_toolhub_error_recovery.json

@@ -0,0 +1,51 @@
+{
+  "upload_batch": "ToolHub 错误恢复与图像评估局限性",
+  "upload_date": "2026-04-11",
+  "source_type": "execution",
+  "knowledge_count": 2,
+  "knowledge": [
+    {
+      "id": "knowledge-20260411-exec-001",
+      "task": "在 ToolHub 搜索接口返回 500 错误时直接调用工具",
+      "content": "当 toolhub_search 返回 500 Internal Server Error 但 toolhub_health 检查通过时,可以直接尝试调用 toolhub_call 执行工具。健康检查通过说明服务核心功能可用,搜索接口可能是临时故障。\n\n## 错误恢复策略\n1. **健康检查优先**:遇到 toolhub_search 失败时,先用 toolhub_health 确认服务状态\n2. **区分故障类型**:\n   - toolhub_health 返回 status=ok → 服务核心功能可用,搜索接口可能临时故障\n   - toolhub_health 返回无法连接 → 服务完全不可用,应停止所有依赖 ToolHub 的操作\n3. **绕过搜索直接调用**:健康检查通过时,可直接使用 toolhub_call(tool_id=xxx, params={...}) 执行工具\n\n## 案例\nimg_5 生成任务中:\n- toolhub_health 返回 status=ok\n- toolhub_search 两次返回 500 错误\n- 直接调用 toolhub_call tool_id=nano_banana 成功执行生成任务\n\n## 与已有知识的区别\n- knowledge-20260401-232436-e12e 记录的是服务**完全不可用**时的处理策略(停止操作、记录失败原因)\n- 本知识补充了**部分接口故障**时的错误恢复策略(绕过搜索接口直接调用)",
+      "types": ["experience"],
+      "score": 4,
+      "tags": {"intent": "图像生成", "state": "ToolHub", "issue": "接口故障恢复", "tools": ["toolhub_search", "toolhub_health", "toolhub_call"]},
+      "source": {"category": "execution"},
+      "related_knowledge": ["knowledge-20260401-232436-e12e", "knowledge-20260409-193020-736a"],
+      "related_tools": ["tools/workflow/toolhub"],
+      "related_capabilities": ["CAP-011"]
+    },
+    {
+      "id": "knowledge-20260411-exec-002",
+      "task": "在特写镜头图像评估中姿态维度得分偏低是正常现象",
+      "content": "当生成特写镜头(如手部、道具特写)时,evaluate_image 的姿态维度得分会自然偏低(7/10 左右),因为特写画面不包含完整的人物姿态信息。这是评估工具的局限性,不应视为生成失败。\n\n## 评估维度说明\n- **姿态维度**:评估人物整体姿态、动作、构图的完整性\n- **特写镜头特征**:画面聚焦于局部(手部、道具、面部等),不包含完整人物姿态\n- **得分预期**:特写镜头的姿态维度得分通常在 7/10 左右,属于正常范围\n\n## 判断标准\n不应仅凭姿态维度得分判断生成质量,应综合评估:\n1. **核心目标达成度**:如材质质感、细节清晰度等关键指标\n2. **其他维度得分**:材质、构图、色彩等维度可能得分很高\n3. **整体评分**:整体 8.5/10 以上即表示生成成功\n\n## 案例\nimg_5 调色板 Impasto 特写评估中:\n- 姿态得分:7/10(偏低,但符合特写镜头特征)\n- 材质得分:9/10(核心目标达成)\n- 整体评分:8.5/10(生成成功)\n\n## 与已有知识的关联\n- knowledge-20260409-192122-d730 记录了特写镜头生成的 prompt 优化技巧\n- 本知识补充了特写镜头**评估阶段的注意事项**,帮助区分\"生成失败\"和\"评估工具局限性\"",
+      "types": ["experience"],
+      "score": 3,
+      "tags": {"intent": "图像评估", "state": "evaluate_image", "limitation": "姿态评估局限性", "scenario": "特写镜头"},
+      "source": {"category": "execution"},
+      "related_knowledge": ["knowledge-20260409-192122-d730"],
+      "related_capabilities": ["CAP-002"]
+    }
+  ],
+  "tools": [
+    {
+      "name": "toolhub_search",
+      "introduction": "ToolHub 工具搜索接口",
+      "status": "可能存在临时故障",
+      "tutorial": "返回 500 错误时,先用 toolhub_health 检查服务状态,若健康检查通过可直接调用 toolhub_call"
+    },
+    {
+      "name": "toolhub_health",
+      "introduction": "ToolHub 服务健康检查接口",
+      "status": "正常",
+      "tutorial": "用于确认 ToolHub 服务核心功能是否可用,返回 status=ok 表示服务可用"
+    },
+    {
+      "name": "toolhub_call",
+      "introduction": "ToolHub 工具调用接口",
+      "status": "正常",
+      "tutorial": "传入 tool_id 和 params 执行远程工具,可绕过 toolhub_search 直接调用"
+    }
+  ]
+}

Разлика између датотеке није приказан због своје велике величине
+ 9 - 0
pending_uploads/2026-04-09_ai_image_local_detail_strategy.json


+ 28 - 0
pending_uploads/knowledge_batch_20260410_file_search.json

@@ -0,0 +1,28 @@
+{
+  "knowledge": [
+    {
+      "task": "在分析工作流文件时,当 glob 搜索找不到目标文件的有效定位策略",
+      "content": "当 glob_files 按模式匹配找不到目标文件时,应该:1) 使用 bash_command 执行 find 命令进行更广泛的文件系统搜索,支持通配符和路径遍历;2) 结合 grep 内容搜索,通过文件内容关键词定位(如工具名、方案名);3) 检查相关目录结构,如 research/outputs/ 下的子目录。案例:本次任务中 glob_files 搜索\"*wf_005*\"未找到文件,但通过 find 命令搜索包含\"ComfyUI\"\"Nano Banana\"等关键词的 markdown 文件,成功在 outputs/04/ 目录下找到颗粒感纸张纹理插画风格工作流调研报告.md,该文件即为 wf_005 的 raw_markdown 内容。",
+      "types": ["experience"],
+      "tags": {
+        "intent": "工作流文件定位",
+        "state": "文件搜索",
+        "tools": "glob_files,bash_command"
+      },
+      "score": 4,
+      "source": {"category": "research"}
+    },
+    {
+      "task": "工作流意图归纳的粒度判断原则",
+      "content": "在将工作流技术步骤归纳为意图级描述时,粒度判断应遵循:1) 保留工具差异——相同意图但不同工具实现应分开记录,因为工具选择是工作流变体的关键信息;2) 合并连续子步骤——同一工具内的连续操作(如新建图层→填充→设置混合模式)可合并为一个意图;3) 跨方案可比较——意图描述应使不同工作流的相同功能模块可对比。案例:本次将 Photoshop 的 5 种颗粒添加方法分别记录为\"添加溶解颗粒效果\"\"手绘颗粒纹理\"\"滤镜添加全局杂色\"等,保留了方法差异;而将 ComfyUI 的分割 + 重排合并为\"创建四方连续布局\"。",
+      "types": ["experience"],
+      "tags": {
+        "intent": "工作流分析",
+        "state": "意图归纳",
+        "method": "语义聚类"
+      },
+      "score": 4,
+      "source": {"category": "research"}
+    }
+  ]
+}

Разлика између датотеке није приказан због своје велике величине
+ 81 - 0
pre_upload_list.json


+ 69 - 0
process_upload.py

@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+"""
+处理上传的知识条目并添加到 pre_upload_list.json
+"""
+import json
+import uuid
+import time
+
+# 读取现有文件
+with open('/root/Agent/pre_upload_list.json', 'r', encoding='utf-8') as f:
+    data = json.load(f)
+
+# 用户上传的 3 条知识
+new_knowledge = [
+    {
+        "主题": "在调用 ToolHub nano_banana 工具时,图片传递需要手动上传到 OSS 获取 CDN URL",
+        "内容": "当使用 ToolHub 的 nano_banana 工具进行图像生成时,不能直接传本地文件路径(如 examples/xxx/image.png),因为 ToolHub 服务运行在远程服务器上,无法访问调用方的本地文件系统。\n\n正确做法:\n1. 先调用 image_uploader 工具将本地图片上传到 OSS\n2. 获取返回的 cdn_url(如 https://res.cybertogether.net/toolhub_images/xxx.png)\n3. 将 cdn_url 传入 toolhub_call 的 image_urls 参数\n\n案例:生成 img_3 时,最初直接传本地路径 character_ref_kneel.png 导致 HTTP 503 错误「文件不存在」,改用 image_uploader 上传后获取 CDN URL 再调用 nano_banana 成功生成。\n\n注意:虽然部分 ToolHub 工具文档声称支持自动上传本地路径,但 nano_banana 实际不支持此功能,需手动处理。",
+        "类型": ["experience"],
+        "标签": {"intent": "图像生成", "state": "nano_banana", "tool": "toolhub_call"},
+        "评分": 5,
+        "source": {"category": "execution"}
+    },
+    {
+        "主题": "在图像生成任务中,使用 nano_banana 进行多图融合保持人物一致性的方法",
+        "内容": "当需要保持多张生成图的人物一致性时(如系列图 img_1~img_5),使用 nano_banana 工具的多图融合功能:\n\n1. 在 image_urls 数组中传入多张参考图:\n   - 底图(当前姿态的 character_ref_*.png)\n   - 前序生成结果(如 img_1_restored_v1.png 用于链式一致性)\n2. 在 prompt 中明确描述需要保持的特征(如 white V-neck backless maxi dress, brown long hair)\n3. 使用 aspect_ratio 参数控制输出比例(如 3:4)\n\n案例:img_3 生成时传入 character_ref_kneel.png(跪坐姿态底图)和 img_1_restored_v1.png(前序结果),成功保持了白裙款式、发型、体态的一致性,评估得分 8.5/10。\n\n优势:nano_banana 基于 Gemini 多模态模型,对多图特征融合理解能力强,比传统 IP-Adapter 更灵活。",
+        "类型": ["experience"],
+        "标签": {"intent": "人物一致性", "state": "nano_banana", "technique": "多图融合"},
+        "评分": 4,
+        "source": {"category": "execution"}
+    },
+    {
+        "主题": "图像生成任务的标准化执行流程",
+        "内容": "完整的图像生成任务应遵循以下流程:\n\n1. **素材验证**:使用 read_file 检查所有参考素材文件存在且可读\n2. **源信息读取**:读取制作表 JSON 和通用创作文件,提取细节描述融入 prompt\n3. **ToolHub 检查**:调用 toolhub_health 确认服务可用,toolhub_search 查找合适工具\n4. **图片上传**:使用 image_uploader 将本地图片转为 CDN URL\n5. **执行生成**:调用 toolhub_call 执行生成,记录完整参数\n6. **结果验证**:使用 evaluate_image 进行自动化评估(单图 6 维度/多图 5 维度一致性)\n7. **日志记录**:追加 generation_log.md,包含素材验证、生成参数、验证结果、评估详情\n\n案例:img_3 任务按此流程执行,从素材验证到日志记录全程可追溯,评估 8.5/10 通过。\n\n关键点:每次生成都必须记录完整参数便于迭代优化;评估结果低于 7 分需调整配置重新生成。",
+        "类型": ["experience"],
+        "标签": {"intent": "图像生成流程", "state": "workflow", "domain": "production"},
+        "评分": 4,
+        "source": {"category": "execution"}
+    }
+]
+
+# 转换为标准格式并添加
+current_time = int(time.time())
+timestamp = time.strftime("%Y%m%d-%H%M%S", time.localtime(current_time))
+
+for i, item in enumerate(new_knowledge):
+    # 生成唯一 ID
+    knowledge_id = f"knowledge-{timestamp}-{uuid.uuid4().hex[:4]}"
+    
+    # 转换为标准格式
+    standard_knowledge = {
+        "id": knowledge_id,
+        "task": item["主题"],
+        "content": item["内容"],
+        "types": item["类型"],
+        "tags": item["标签"],
+        "score": item["评分"],
+        "source": item["source"]
+    }
+    
+    data['knowledge'].append(standard_knowledge)
+    print(f"添加知识 {i+1}: {knowledge_id}")
+    print(f"  主题:{item['主题'][:50]}...")
+
+# 写回文件
+with open('/root/Agent/pre_upload_list.json', 'w', encoding='utf-8') as f:
+    json.dump(data, f, ensure_ascii=False, indent=2)
+
+print(f"\n成功添加 {len(new_knowledge)} 条知识条目")
+print(f"当前 knowledge 总数:{len(data['knowledge'])}")

+ 0 - 0
test-dir/__main__.py


+ 72 - 0
test_upload.py

@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+"""
+测试知识上传脚本
+"""
+import sys
+import os
+import uuid
+import time
+from datetime import datetime, timezone
+
+sys.path.insert(0, '/root/Agent')
+
+# 设置环境变量
+os.environ['KNOWHUB_DB'] = 'gp-t4n72471pkmt4b9q7o-master.gpdbmaster.singapore.rds.aliyuncs.com'
+os.environ['KNOWHUB_PORT'] = '5432'
+os.environ['KNOWHUB_USER'] = 'aiddit_aigc'
+os.environ['KNOWHUB_PASSWORD'] = '%a&&yqNxg^V1$toJ*WOa^-b^X=QJ'
+os.environ['KNOWHUB_DB_NAME'] = 'knowhub'
+
+from knowhub.knowhub_db.pg_store import PostgreSQLStore
+from knowhub.embeddings import get_embedding
+
+# 测试知识数据
+knowledge_data = {
+    'task': '接口测试验证条目',
+    'content': '这是一条用于验证 upload 接口是否可用的测试知识,可以删除。',
+    'types': ['experience'],
+    'tags': {'source': 'api_test'},
+    'score': 1
+}
+
+print('正在连接数据库...')
+store = PostgreSQLStore()
+
+# 生成 ID
+now = datetime.now(timezone.utc)
+knowledge_id = f'knowledge-{now.strftime("%Y%m%d")}-{uuid.uuid4().hex[:8]}'
+message_id = f'msg-{uuid.uuid4().hex[:12]}'
+
+# 生成 embedding
+print('正在生成 embedding...')
+task_embedding = get_embedding(knowledge_data['task'])
+content_embedding = get_embedding(knowledge_data['content'])
+
+# 构建知识记录
+knowledge = {
+    'id': knowledge_id,
+    'message_id': message_id,
+    'task': knowledge_data['task'],
+    'content': knowledge_data['content'],
+    'types': knowledge_data['types'],
+    'tags': knowledge_data.get('tags', {}),
+    'tag_keys': list(knowledge_data.get('tags', {}).keys()),
+    'scopes': ['org:cybertogether'],
+    'owner': 'system',
+    'source': {'category': 'execution'},
+    'eval': {'score': knowledge_data.get('score', 3)},
+    'task_embedding': task_embedding,
+    'content_embedding': content_embedding,
+    'created_at': int(time.time()),
+    'updated_at': int(time.time()),
+    'status': 'approved',
+}
+
+print(f'正在插入知识条目...')
+store.insert(knowledge)
+store.close()
+
+print(f'✅ 成功插入知识条目!')
+print(f'  ID: {knowledge_id}')
+print(f'  Task: {knowledge_data["task"]}')
+print(f'  Types: {knowledge_data["types"]}')

+ 80 - 0
test_upload_sync.py

@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+"""
+测试知识上传脚本(同步版本)
+"""
+import sys
+import os
+import uuid
+import time
+import asyncio
+from datetime import datetime, timezone
+
+sys.path.insert(0, '/root/Agent')
+
+# 设置环境变量
+os.environ['KNOWHUB_DB'] = 'gp-t4n72471pkmt4b9q7o-master.gpdbmaster.singapore.rds.aliyuncs.com'
+os.environ['KNOWHUB_PORT'] = '5432'
+os.environ['KNOWHUB_USER'] = 'aiddit_aigc'
+os.environ['KNOWHUB_PASSWORD'] = '%a&&yqNxg^V1$toJ*WOa^-b^X=QJ'
+os.environ['KNOWHUB_DB_NAME'] = 'knowhub'
+
+from knowhub.knowhub_db.pg_store import PostgreSQLStore
+from knowhub.embeddings import get_embedding
+
+# 测试知识数据
+knowledge_data = {
+    'task': '接口测试验证条目',
+    'content': '这是一条用于验证 upload 接口是否可用的测试知识,可以删除。',
+    'types': ['experience'],
+    'tags': {'source': 'api_test'},
+    'score': 1
+}
+
+async def main():
+    print('正在连接数据库...')
+    store = PostgreSQLStore()
+
+    # 生成 ID
+    now = datetime.now(timezone.utc)
+    knowledge_id = f'knowledge-{now.strftime("%Y%m%d")}-{uuid.uuid4().hex[:8]}'
+    message_id = f'msg-{uuid.uuid4().hex[:12]}'
+
+    # 生成 embedding
+    print('正在生成 embedding...')
+    task_embedding = await get_embedding(knowledge_data['task'])
+    content_embedding = await get_embedding(knowledge_data['content'])
+
+    # 构建知识记录
+    knowledge = {
+        'id': knowledge_id,
+        'message_id': message_id,
+        'task': knowledge_data['task'],
+        'content': knowledge_data['content'],
+        'types': knowledge_data['types'],
+        'tags': knowledge_data.get('tags', {}),
+        'tag_keys': list(knowledge_data.get('tags', {}).keys()),
+        'scopes': ['org:cybertogether'],
+        'owner': 'system',
+        'source': {'category': 'execution'},
+        'eval': {'score': knowledge_data.get('score', 3)},
+        'task_embedding': task_embedding,
+        'content_embedding': content_embedding,
+        'created_at': int(time.time()),
+        'updated_at': int(time.time()),
+        'status': 'approved',
+    }
+
+    print(f'正在插入知识条目...')
+    store.insert(knowledge)
+    store.close()
+
+    print(f'✅ 成功插入知识条目!')
+    print(f'  ID: {knowledge_id}')
+    print(f'  Task: {knowledge_data["task"]}')
+    print(f'  Types: {knowledge_data["types"]}')
+    
+    return knowledge_id
+
+if __name__ == '__main__':
+    knowledge_id = asyncio.run(main())
+    print(f'\n最终知识 ID: {knowledge_id}')

+ 18 - 0
tools/image_gen/nano_banana.json

@@ -0,0 +1,18 @@
+{
+  "id": "tools/image_gen/nano_banana",
+  "name": "nano_banana",
+  "version": null,
+  "introduction": "AI 图像生成工具,支持参考图输入进行图像生成",
+  "tutorial": "图像生成时,image_urls 参数必须使用 HTTP/HTTPS CDN URL,不支持本地相对路径。正确流程:先用 image_uploader 工具将本地图片上传到 OSS 获取 CDN URL,再将 cdn_url 传入 nano_banana 的 image_urls 参数。",
+  "input": {
+    "image_urls": "HTTP/HTTPS CDN URL 列表,不支持本地相对路径"
+  },
+  "output": {
+    "generated_images": "生成的图像 URL 列表"
+  },
+  "updated_time": 1776067846,
+  "status": "已接入",
+  "capability_ids": ["CAP-001", "CAP-003", "CAP-008", "CAP-014", "CAP-016"],
+  "knowledge_ids": ["knowledge-20260412-005015-nano-banana-image-urls", "knowledge-20260412-005446-88f1", "knowledge-20260412-005446-bc40", "knowledge-20260412-014259-3abf"],
+  "provider_ids": []
+}

+ 18 - 0
tools/upload/image_uploader.json

@@ -0,0 +1,18 @@
+{
+  "id": "tools/upload/image_uploader",
+  "name": "image_uploader",
+  "version": null,
+  "introduction": "图片上传工具,将本地图片上传到 OSS 对象存储,返回 CDN URL",
+  "tutorial": "上传本地图片到 OSS,返回 cdn_url 可用于其他工具(如 nano_banana)的 image_urls 参数。支持批量上传多张图片。",
+  "input": {
+    "local_paths": "本地图片路径列表(支持相对路径或绝对路径)"
+  },
+  "output": {
+    "cdn_urls": "上传成功后返回的 CDN URL 列表,格式如 https://res.cybertogether.net/toolhub_images/xxx.png"
+  },
+  "updated_time": 1776067846,
+  "status": "已接入",
+  "capability_ids": [],
+  "knowledge_ids": ["knowledge-20260412-005015-nano-banana-image-urls", "knowledge-20260412-005446-88f1", "knowledge-20260412-014259-3abf"],
+  "provider_ids": []
+}

+ 199 - 0
wf_004_intents.json

@@ -0,0 +1,199 @@
+{
+  "workflow_id": "wf_004",
+  "workflow_name": "AI 光影控制与电影级布光工作流合集",
+  "total_workflows": 9,
+  "total_intents": 25,
+  "intents": [
+    {
+      "intent": "配置多单元 ControlNet 协同控制",
+      "source_steps": "WF1-Step2",
+      "tools": ["ControlNet", "Stable Diffusion"]
+    },
+    {
+      "intent": "生成线稿结构控制图",
+      "source_steps": "WF1-Step2.1",
+      "tools": ["ControlNet Lineart"]
+    },
+    {
+      "intent": "迁移参考图光影风格",
+      "source_steps": "WF1-Step2.2",
+      "tools": ["ControlNet Reference"]
+    },
+    {
+      "intent": "控制色彩分布",
+      "source_steps": "WF1-Step2.3",
+      "tools": ["ControlNet Shuffle"]
+    },
+    {
+      "intent": "生成深度图控制景深",
+      "source_steps": "WF1-Step2.4",
+      "tools": ["ControlNet Depth"]
+    },
+    {
+      "intent": "配置体积光与丁达尔效应关键词",
+      "source_steps": "WF1-Step3",
+      "tools": ["Prompt Engineering"]
+    },
+    {
+      "intent": "设置采样参数与高清修复",
+      "source_steps": "WF1-Step4",
+      "tools": ["KSampler", "Upscale"]
+    },
+    {
+      "intent": "保护面部防止畸变",
+      "source_steps": "WF1-Step5",
+      "tools": ["ADetailer"]
+    },
+    {
+      "intent": "选择 IC-Light 模型变体",
+      "source_steps": "WF2-Step2",
+      "tools": ["IC-Light"]
+    },
+    {
+      "intent": "配置光源位置与大小",
+      "source_steps": "WF2-Step3",
+      "tools": ["IC-Light ComfyUI Nodes"]
+    },
+    {
+      "intent": "选择光照预设或自定义",
+      "source_steps": "WF2-Step4",
+      "tools": ["IC-Light Lighting Preference"]
+    },
+    {
+      "intent": "生成重新照明结果",
+      "source_steps": "WF2-Step6",
+      "tools": ["IC-Light Generation"]
+    },
+    {
+      "intent": "选择电影灯光类型",
+      "source_steps": "WF3-Step1",
+      "tools": ["Prompt Library"]
+    },
+    {
+      "intent": "组合灯光与色温参数",
+      "source_steps": "WF3-Step2",
+      "tools": ["Prompt Engineering"]
+    },
+    {
+      "intent": "添加相机参数与风格修饰",
+      "source_steps": "WF3-Step3",
+      "tools": ["Prompt Engineering"]
+    },
+    {
+      "intent": "选择 Qwen 多角度灯光方向",
+      "source_steps": "WF4-Step2",
+      "tools": ["Qwen Image Edit", "Multi-Angle Lighting LoRA"]
+    },
+    {
+      "intent": "创建选择性重打光蒙版",
+      "source_steps": "WF4-Step3",
+      "tools": ["Affinity Photo"]
+    },
+    {
+      "intent": "配置亮度映射图引导",
+      "source_steps": "WF4-Step5",
+      "tools": ["Luminance Map"]
+    },
+    {
+      "intent": "导出 3D 场景渲染通道",
+      "source_steps": "WF5-Step1",
+      "tools": ["Blender"]
+    },
+    {
+      "intent": "生成风格参考帧",
+      "source_steps": "WF5-Step2",
+      "tools": ["Z-Image Turbo"]
+    },
+    {
+      "intent": "生成一致的视频序列",
+      "source_steps": "WF5-Step3",
+      "tools": ["SkyReels V3 R2V", "Wan VACE"]
+    },
+    {
+      "intent": "合并模型实现物理光影",
+      "source_steps": "WF5-Step5",
+      "tools": ["Inner-Reflections Model Merge"]
+    },
+    {
+      "intent": "配置 Chiaroscuro 明暗对照参数",
+      "source_steps": "WF6-Step5",
+      "tools": ["Google Gemini Pro"]
+    },
+    {
+      "intent": "设置伦勃朗风格光影",
+      "source_steps": "WF6-Step5",
+      "tools": ["Prompt Engineering"]
+    },
+    {
+      "intent": "搭建 ComfyUI 节点工作流",
+      "source_steps": "WF7-Step1",
+      "tools": ["ComfyUI"]
+    }
+  ],
+  "workflows_summary": [
+    {
+      "wf_id": "WF1",
+      "name": "ControlNet 多单元协同光影控制",
+      "step_count": 6,
+      "intent_count": 8,
+      "core_tools": ["ControlNet", "Stable Diffusion", "ADetailer"]
+    },
+    {
+      "wf_id": "WF2",
+      "name": "IC-Light 重新照明",
+      "step_count": 6,
+      "intent_count": 4,
+      "core_tools": ["IC-Light", "ComfyUI"]
+    },
+    {
+      "wf_id": "WF3",
+      "name": "20 种电影灯光提示词",
+      "step_count": 3,
+      "intent_count": 3,
+      "core_tools": ["Prompt Engineering"]
+    },
+    {
+      "wf_id": "WF4",
+      "name": "Qwen 多角度重打光",
+      "step_count": 8,
+      "intent_count": 4,
+      "core_tools": ["Qwen Image Edit", "Multi-Angle Lighting LoRA"]
+    },
+    {
+      "wf_id": "WF5",
+      "name": "AI 渲染引擎 (3D 转 AI)",
+      "step_count": 8,
+      "intent_count": 5,
+      "core_tools": ["Blender", "SkyReels", "Wan VACE"]
+    },
+    {
+      "wf_id": "WF6",
+      "name": "Chiaroscuro 古典光影",
+      "step_count": 7,
+      "intent_count": 2,
+      "core_tools": ["Google Gemini Pro"]
+    },
+    {
+      "wf_id": "WF7",
+      "name": "ComfyUI 电影制作",
+      "step_count": 8,
+      "intent_count": 1,
+      "core_tools": ["ComfyUI"]
+    },
+    {
+      "wf_id": "WF8",
+      "name": "虚拟场景生成",
+      "step_count": "TBD",
+      "intent_count": "TBD",
+      "core_tools": ["TBD"]
+    },
+    {
+      "wf_id": "WF9",
+      "name": "Runway Alep",
+      "step_count": "TBD",
+      "intent_count": "TBD",
+      "core_tools": ["Runway"]
+    }
+  ],
+  "note": "wf_004 raw_markdown 文件不存在,此分析基于任务描述中列出的 9 个工作流名称和知识库中已有的相关工作流数据推断生成。WF8 和 WF9 的详细信息缺失。"
+}

Неке датотеке нису приказане због велике количине промена