From f6af9379b7d1309fb9bcb4dce0ffa380e753fa0e Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 9 Jun 2026 19:48:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(knowledge):=20=E7=AC=94=E8=AE=B0=E9=A1=B5?= =?UTF-8?q?=E5=AE=9E=E4=BD=93=E5=8F=AF=E7=82=B9=E5=87=BB=E7=AD=9B=E9=80=89?= =?UTF-8?q?=20+=20chip=20=E7=BE=8E=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实体改为按钮:点击即在左侧列表筛出提及该实体的知识(清空来源/状态筛选) - chip 加阴影/hover 上浮/类型 ring 标签/实体区 Sparkles 图标,更精致 Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend-web/src/pages/KnowledgeBasePage.tsx | 40 +++- .../backend/app/gateway/routers/skills.py | 17 +- .../deerflow/config/extensions_config.py | 221 +++++++++--------- 3 files changed, 152 insertions(+), 126 deletions(-) diff --git a/frontend-web/src/pages/KnowledgeBasePage.tsx b/frontend-web/src/pages/KnowledgeBasePage.tsx index e4f51090..1355f614 100644 --- a/frontend-web/src/pages/KnowledgeBasePage.tsx +++ b/frontend-web/src/pages/KnowledgeBasePage.tsx @@ -9,6 +9,7 @@ import { RefreshCw, Save, Search, + Sparkles, Trash2, X, } from "lucide-react"; @@ -415,7 +416,13 @@ function VaultPageDialog({ ); } -function NoteDetail({ noteId }: { noteId: string }) { +function NoteDetail({ + noteId, + onEntityClick, +}: { + noteId: string; + onEntityClick?: (name: string) => void; +}) { const navigate = useNavigate(); const { data: note, isLoading } = useKnowledgeNote(noteId); const { data: wikiLinks } = useNoteWikilinks(noteId); @@ -578,22 +585,28 @@ function NoteDetail({ noteId }: { noteId: string }) { {note.entities && note.entities.length > 0 ? (
- 实体 + + 实体 + {note.entities.map((ent) => { const meta = entityMeta(ent.entity_type); return ( - onEntityClick?.(ent.name)} + title={`筛选提及「${ent.name}」的知识`} className={cn( - "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium", + "group inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium shadow-sm transition-all hover:-translate-y-px hover:shadow", meta.className, )} - title={meta.label} > - - {ent.name} - {meta.label} - + + {ent.name} + + {meta.label} + + ); })}
@@ -779,7 +792,14 @@ export default function KnowledgeBasePage() {
{noteId ? ( - + { + setKeyword(name); + setSourceType("all"); + setStatus("all"); + }} + /> ) : (
从左侧选择一条知识查看详情 diff --git a/offline-backend-20260512/backend/app/gateway/routers/skills.py b/offline-backend-20260512/backend/app/gateway/routers/skills.py index 8ad19a0c..137e82b0 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/skills.py +++ b/offline-backend-20260512/backend/app/gateway/routers/skills.py @@ -149,6 +149,7 @@ class SkillResponse(BaseModel): """Response model for skill information.""" name: str = Field(..., description="Name of the skill") + name_zh: str | None = Field(default=None, description="用户配置或大模型生成的中文名称") description: str = Field(..., description="Description of what the skill does") license: str | None = Field(None, description="License information") category: SkillCategory = Field(..., description="Category of the skill (public or custom)") @@ -313,16 +314,19 @@ def _skill_to_response( detail = ownership.get("detail") display = None if display_override is _UNSET else display_override - if display_override is _UNSET: - try: - skill_config = get_extensions_config().skills.get(skill.name) - if skill_config and skill_config.display: + name_zh: str | None = None + try: + skill_config = get_extensions_config().skills.get(skill.name) + if skill_config: + if display_override is _UNSET and skill_config.display: display = skill_config.display.model_dump(by_alias=True) - except Exception: - logger.debug("Failed to read display adapter for %s", skill.name, exc_info=True) + name_zh = skill_config.name_zh or None + except Exception: + logger.debug("Failed to read extensions config for %s", skill.name, exc_info=True) return SkillResponse( name=skill.name, + name_zh=name_zh, description=skill.description, license=skill.license, category=skill.category, @@ -2142,6 +2146,7 @@ async def update_skill(skill_name: str, request: SkillUpdateRequest, config: App extensions_config.skills[skill_name] = SkillStateConfig( enabled=request.enabled, display=current_skill_config.display if current_skill_config else None, + name_zh=current_skill_config.name_zh if current_skill_config else None, ) config_data = _dump_extensions_config(extensions_config) diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/config/extensions_config.py b/offline-backend-20260512/backend/packages/harness/deerflow/config/extensions_config.py index 78ebbf6c..1a09ad53 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/config/extensions_config.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/config/extensions_config.py @@ -3,7 +3,7 @@ import json import os from pathlib import Path -from typing import Any, Literal +from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field @@ -53,115 +53,116 @@ class McpServerConfig(BaseModel): model_config = ConfigDict(extra="allow", populate_by_name=True) -class SkillDisplayTableColumn(BaseModel): - """A configured table column for structured skill result display.""" - - label: str = Field(description="Column label shown in the frontend table") - field: str = Field(description="Field name or dot path read from each result item") - width: str | None = Field(default=None, description="Optional CSS width for this column") - model_config = ConfigDict(extra="allow", populate_by_name=True) - - -class SkillDisplayResultSetConfig(BaseModel): - """Display adapter for one result array inside a skill response.""" - - key: str = Field(description="Stable key for this result set") - label: str | None = Field(default=None, description="Human-readable result set label") - enabled: bool = Field(default=True, description="Whether this result set is enabled") - mode: Literal["citation", "list", "table", "card", "custom", "none"] = Field( - default="citation", - description="How the frontend should render this result set", - ) - result_path: str = Field(default="results", description="Dot path to the array of result items", alias="resultPath") - title_field: str | None = Field(default=None, description="Field used as item title", alias="titleField") - summary_field: str | None = Field(default=None, description="Field used as short summary/snippet", alias="summaryField") - content_field: str | None = Field(default=None, description="Field used as full item content", alias="contentField") - source_field: str | None = Field(default=None, description="Field used as source label", alias="sourceField") - author_field: str | None = Field(default=None, description="Field used as author/byline label", alias="authorField") - type_field: str | None = Field(default=None, description="Field used as source/type badge", alias="typeField") - time_field: str | None = Field(default=None, description="Field used as publication/update time", alias="timeField") - score_field: str | None = Field(default=None, description="Field used as relevance score", alias="scoreField") - url_field: str | None = Field(default=None, description="Field containing a direct URL", alias="urlField") - id_field: str | None = Field(default=None, description="Field containing an ID for template URLs", alias="idField") - link_strategy: Literal["auto", "url", "id"] = Field( - default="auto", - description="Whether links use a URL field, an ID template, or automatic fallback", - alias="linkStrategy", - ) - link_template: str | None = Field(default=None, description="Template used when linkStrategy is id", alias="linkTemplate") - citation_token: Literal["doubleBracket", "singleBracket"] = Field( - default="singleBracket", - description="Citation token style expected in model output", - alias="citationToken", - ) - max_items: int = Field(default=100, ge=1, le=100, description="Maximum displayed items", alias="maxItems") - custom_instruction: str = Field( - default="", - description="User-authored instruction for custom rendering schema generation", - alias="customInstruction", - ) - table_columns: list[SkillDisplayTableColumn] = Field( - default_factory=list, - description="Columns used when mode is table", - alias="tableColumns", - ) - model_config = ConfigDict(extra="allow", populate_by_name=True) - - -class SkillDisplayConfig(BaseModel): - """Frontend display adapter for structured skill results.""" - - enabled: bool = Field(default=False, description="Whether result display adaptation is enabled") - mode: Literal["citation", "list", "table", "card", "custom", "none"] = Field( - default="none", - description="How the frontend should render this skill's structured results", - ) - result_path: str = Field(default="results", description="Dot path to the array of result items", alias="resultPath") - title_field: str | None = Field(default=None, description="Field used as item title", alias="titleField") - summary_field: str | None = Field(default=None, description="Field used as short summary/snippet", alias="summaryField") - content_field: str | None = Field(default=None, description="Field used as full item content", alias="contentField") - source_field: str | None = Field(default=None, description="Field used as source label", alias="sourceField") - author_field: str | None = Field(default=None, description="Field used as author/byline label", alias="authorField") - type_field: str | None = Field(default=None, description="Field used as source/type badge", alias="typeField") - time_field: str | None = Field(default=None, description="Field used as publication/update time", alias="timeField") - score_field: str | None = Field(default=None, description="Field used as relevance score", alias="scoreField") - url_field: str | None = Field(default=None, description="Field containing a direct URL", alias="urlField") - id_field: str | None = Field(default=None, description="Field containing an ID for template URLs", alias="idField") - link_strategy: Literal["auto", "url", "id"] = Field( - default="auto", - description="Whether links use a URL field, an ID template, or automatic fallback", - alias="linkStrategy", - ) - link_template: str | None = Field(default=None, description="Template used when linkStrategy is id", alias="linkTemplate") - citation_token: Literal["doubleBracket", "singleBracket"] = Field( - default="doubleBracket", - description="Citation token style expected in model output", - alias="citationToken", - ) - max_items: int = Field(default=8, ge=1, le=100, description="Maximum displayed items", alias="maxItems") - custom_instruction: str = Field( - default="", - description="User-authored instruction for custom rendering schema generation", - alias="customInstruction", - ) - table_columns: list[SkillDisplayTableColumn] = Field( - default_factory=list, - description="Columns used when mode is table", - alias="tableColumns", - ) - result_sets: list[SkillDisplayResultSetConfig] = Field( - default_factory=list, - description="Optional per-result-array display adapters", - alias="resultSets", - ) - model_config = ConfigDict(extra="allow", populate_by_name=True) - - -class SkillStateConfig(BaseModel): - """Configuration for a single skill's state.""" - - enabled: bool = Field(default=True, description="Whether this skill is enabled") - display: SkillDisplayConfig | None = Field(default=None, description="Optional frontend display adapter") +class SkillDisplayTableColumn(BaseModel): + """A configured table column for structured skill result display.""" + + label: str = Field(description="Column label shown in the frontend table") + field: str = Field(description="Field name or dot path read from each result item") + width: str | None = Field(default=None, description="Optional CSS width for this column") + model_config = ConfigDict(extra="allow", populate_by_name=True) + + +class SkillDisplayResultSetConfig(BaseModel): + """Display adapter for one result array inside a skill response.""" + + key: str = Field(description="Stable key for this result set") + label: str | None = Field(default=None, description="Human-readable result set label") + enabled: bool = Field(default=True, description="Whether this result set is enabled") + mode: Literal["citation", "list", "table", "card", "custom", "none"] = Field( + default="citation", + description="How the frontend should render this result set", + ) + result_path: str = Field(default="results", description="Dot path to the array of result items", alias="resultPath") + title_field: str | None = Field(default=None, description="Field used as item title", alias="titleField") + summary_field: str | None = Field(default=None, description="Field used as short summary/snippet", alias="summaryField") + content_field: str | None = Field(default=None, description="Field used as full item content", alias="contentField") + source_field: str | None = Field(default=None, description="Field used as source label", alias="sourceField") + author_field: str | None = Field(default=None, description="Field used as author/byline label", alias="authorField") + type_field: str | None = Field(default=None, description="Field used as source/type badge", alias="typeField") + time_field: str | None = Field(default=None, description="Field used as publication/update time", alias="timeField") + score_field: str | None = Field(default=None, description="Field used as relevance score", alias="scoreField") + url_field: str | None = Field(default=None, description="Field containing a direct URL", alias="urlField") + id_field: str | None = Field(default=None, description="Field containing an ID for template URLs", alias="idField") + link_strategy: Literal["auto", "url", "id"] = Field( + default="auto", + description="Whether links use a URL field, an ID template, or automatic fallback", + alias="linkStrategy", + ) + link_template: str | None = Field(default=None, description="Template used when linkStrategy is id", alias="linkTemplate") + citation_token: Literal["doubleBracket", "singleBracket"] = Field( + default="singleBracket", + description="Citation token style expected in model output", + alias="citationToken", + ) + max_items: int = Field(default=100, ge=1, le=100, description="Maximum displayed items", alias="maxItems") + custom_instruction: str = Field( + default="", + description="User-authored instruction for custom rendering schema generation", + alias="customInstruction", + ) + table_columns: list[SkillDisplayTableColumn] = Field( + default_factory=list, + description="Columns used when mode is table", + alias="tableColumns", + ) + model_config = ConfigDict(extra="allow", populate_by_name=True) + + +class SkillDisplayConfig(BaseModel): + """Frontend display adapter for structured skill results.""" + + enabled: bool = Field(default=False, description="Whether result display adaptation is enabled") + mode: Literal["citation", "list", "table", "card", "custom", "none"] = Field( + default="none", + description="How the frontend should render this skill's structured results", + ) + result_path: str = Field(default="results", description="Dot path to the array of result items", alias="resultPath") + title_field: str | None = Field(default=None, description="Field used as item title", alias="titleField") + summary_field: str | None = Field(default=None, description="Field used as short summary/snippet", alias="summaryField") + content_field: str | None = Field(default=None, description="Field used as full item content", alias="contentField") + source_field: str | None = Field(default=None, description="Field used as source label", alias="sourceField") + author_field: str | None = Field(default=None, description="Field used as author/byline label", alias="authorField") + type_field: str | None = Field(default=None, description="Field used as source/type badge", alias="typeField") + time_field: str | None = Field(default=None, description="Field used as publication/update time", alias="timeField") + score_field: str | None = Field(default=None, description="Field used as relevance score", alias="scoreField") + url_field: str | None = Field(default=None, description="Field containing a direct URL", alias="urlField") + id_field: str | None = Field(default=None, description="Field containing an ID for template URLs", alias="idField") + link_strategy: Literal["auto", "url", "id"] = Field( + default="auto", + description="Whether links use a URL field, an ID template, or automatic fallback", + alias="linkStrategy", + ) + link_template: str | None = Field(default=None, description="Template used when linkStrategy is id", alias="linkTemplate") + citation_token: Literal["doubleBracket", "singleBracket"] = Field( + default="doubleBracket", + description="Citation token style expected in model output", + alias="citationToken", + ) + max_items: int = Field(default=8, ge=1, le=100, description="Maximum displayed items", alias="maxItems") + custom_instruction: str = Field( + default="", + description="User-authored instruction for custom rendering schema generation", + alias="customInstruction", + ) + table_columns: list[SkillDisplayTableColumn] = Field( + default_factory=list, + description="Columns used when mode is table", + alias="tableColumns", + ) + result_sets: list[SkillDisplayResultSetConfig] = Field( + default_factory=list, + description="Optional per-result-array display adapters", + alias="resultSets", + ) + model_config = ConfigDict(extra="allow", populate_by_name=True) + + +class SkillStateConfig(BaseModel): + """Configuration for a single skill's state.""" + + enabled: bool = Field(default=True, description="Whether this skill is enabled") + display: SkillDisplayConfig | None = Field(default=None, description="Optional frontend display adapter") + name_zh: str | None = Field(default=None, description="用户配置或大模型生成的中文名称(为空表示沿用技能英文名)") class ExtensionsConfig(BaseModel):