chore: 提交本地修改

This commit is contained in:
Administrator committed 2026-06-12 18:09:07 +08:00
1 parent 229830ee89
commit 2862eee33b
19 files changed
+78 -218

No files matched your search

Submodule _skills_src/ai-skills added at cde1c8385e.
Submodule _skills_src/anthropics-skills added at 5754626092.
Submodule _skills_src/awesome-claude-skills added at 92568c1eda.
Submodule _skills_src/ce-kit added at d4973c4827.
+1
Submodule _skills_src/csm added at 3fa16a94e0.
Submodule _skills_src/superpowers added at 6fd4507659.
Submodule _skills_src/tapestry-skills added at 80e1dc56df.
@@ -65,28 +65,6 @@ import { usePublishSquares } from "@/core/system-settings/hooks";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
/**
* Turn the admin-authored SOUL.md markdown into a clean, readable profile blurb
* for the narrow panel. The view mode is a profile card, not a document view —
* so we strip markdown syntax (headings, emphasis, inline code, list bullets,
* blockquotes) rather than rendering it, keeping the line structure intact.
*/
function cleanProfileText(raw: string): string {
return raw
.split("\n")
.map((line) =>
line
.replace(/^\s{0,3}#{1,6}\s*/, "")
.replace(/^\s{0,3}>\s?/, "")
.replace(/^(\s*)[-*+]\s+/, "$1• ")
.replace(/(\*\*|__)(.*?)\1/g, "$2")
.replace(/(\*|_)(.*?)\1/g, "$2")
.replace(/`([^`]+)`/g, "$1"),
)
.join("\n")
.replace(/\n{3,}/g, "\n\n")
.trim();
}
/** Local edit-mode draft — committed in one `updateAgent` call on 保存. */
interface AgentDraft {
@@ -232,9 +210,6 @@ export function AgentDetailPanel({
() => normalizeAgentQuestions(agent?.common_questions),
[agent?.common_questions],
);
const soulPreview = agent?.soul?.trim()
? cleanProfileText(desensitize(agent.soul))
: undefined;
function beginEdit() {
if (!agent || !canEdit) return;
@@ -609,31 +584,6 @@ export function AgentDetailPanel({
)}
</section>
{/* ── SOUL / 人设 ────────────────────────────────────────────────── */}
{(editing || soulPreview) && (
<Section
icon={<SparklesIcon className="size-3.5" />}
title="人设(SOUL)"
>
{editing && draft ? (
<Textarea
value={draft.soul}
onChange={(e) =>
setDraft({ ...draft, soul: e.currentTarget.value })
}
placeholder="输入 SOUL.md 内容"
className="max-h-[45vh] min-h-48 resize-y font-mono text-xs"
/>
) : (
// Viewport-relative cap: the SOUL preview expands with the screen
// (taller than the old fixed 240px box) but never swallows the
// whole panel — long content still scrolls inside.
<p className="text-muted-foreground max-h-[40vh] overflow-y-auto whitespace-pre-wrap text-sm leading-relaxed">
{soulPreview}
</p>
)}
</Section>
)}
{/* ── 常用问题 ───────────────────────────────────────────────────── */}
{(editing || questions.length > 0 || canEdit) && (
@@ -4,16 +4,15 @@ import {
ArrowDownIcon,
Code2Icon,
CopyIcon,
DownloadIcon,
EyeIcon,
FileType2Icon,
LoaderIcon,
PackageIcon,
SaveIcon,
SquareArrowOutUpRightIcon,
UploadCloudIcon,
XIcon,
} from "lucide-react";
DownloadIcon,
EyeIcon,
FileType2Icon,
LoaderIcon,
PackageIcon,
SaveIcon,
UploadCloudIcon,
XIcon,
} from "lucide-react";
import { useParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from "react";
@@ -38,12 +37,11 @@ import {
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { CodeEditor } from "@/components/workspace/code-editor";
import { publishArtifactToKnowledge } from "@/core/agents/api";
import { useAgent } from "@/core/agents/hooks";
import { apiFetch } from "@/core/api";
import {
downloadArtifactFile,
openArtifactInNewTab,
} from "@/core/artifacts/download";
import { useAgent } from "@/core/agents/hooks";
import { apiFetch } from "@/core/api";
import {
downloadArtifactFile,
} from "@/core/artifacts/download";
import {
useArtifactContent,
useUpdateArtifactContent,
@@ -514,32 +512,7 @@ export function ArtifactFileDetail({
}
}, [displayContent, filepath]);
const handleOpenWriteFileInNewTab = useCallback(() => {
const source = displayContent ?? "";
if (!source.trim()) {
toast.error("文件内容为空,暂时无法打开");
return;
}
if (language === "mermaid") {
void openMermaidSvgInNewTab(source).catch((error) => {
console.error("Failed to open mermaid artifact:", error);
toast.error(error instanceof Error ? error.message : "Failed to open artifact");
});
return;
}
const blobType =
language === "html"
? "text/html;charset=utf-8"
: language === "markdown"
? "text/markdown;charset=utf-8"
: "text/plain;charset=utf-8";
const blob = new Blob([source], { type: blobType });
const url = URL.createObjectURL(blob);
window.open(url, "_blank", "noopener,noreferrer");
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
}, [displayContent, language]);
const handleDownloadWriteFile = useCallback(() => {
const handleDownloadWriteFile = useCallback(() => {
const source = displayContent ?? "";
if (!source.trim()) {
toast.error("文件内容为空,暂时无法下载");
@@ -571,7 +544,7 @@ export function ArtifactFileDetail({
return (
<Artifact className={cn(className)}>
<ArtifactHeader className="grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 px-4 py-3">
<ArtifactHeader className="flex flex-wrap items-center gap-3 px-4 py-3">
<div className="flex min-w-0 items-center gap-2">
<div className="bg-muted/65 inline-flex min-w-0 items-center rounded-full border border-black/6 p-1 shadow-sm dark:border-white/8">
<span className="bg-background text-muted-foreground inline-flex h-7 shrink-0 items-center rounded-full border border-black/6 px-2.5 text-[10px] font-semibold tracking-[0.16em] dark:border-white/8">
@@ -601,7 +574,7 @@ export function ArtifactFileDetail({
</ArtifactTitle>
</div>
</div>
<div className="flex items-center justify-center">
<div className="flex w-full items-center justify-center md:w-auto md:flex-1">
{isSupportPreview && (
<div className="flex items-center gap-2">
<ToggleGroup
@@ -644,8 +617,8 @@ export function ArtifactFileDetail({
</div>
)}
</div>
<div className="flex items-center justify-end gap-2">
<ArtifactActions className="bg-muted/45 rounded-full border border-black/6 p-1 dark:border-white/8">
<div className="flex w-full items-center justify-end gap-2 md:ml-auto md:w-auto">
<ArtifactActions className="bg-muted/45 max-w-full flex-wrap justify-end rounded-3xl border border-black/6 p-1 dark:border-white/8">
{canPublishToKnowledge && (
<ArtifactAction
icon={isPublishingToKnowledge ? LoaderIcon : UploadCloudIcon}
@@ -678,45 +651,6 @@ export function ArtifactFileDetail({
/>
</Tooltip>
)}
<ArtifactAction
icon={SquareArrowOutUpRightIcon}
label={t.common.openInNewWindow}
tooltip={t.common.openInNewWindow}
onClick={async () => {
if (useWriteFileStreaming) {
handleOpenWriteFileInNewTab();
return;
}
if (language === "mermaid") {
try {
await openMermaidSvgInNewTab(displayContent ?? "");
} catch (error) {
console.error(error);
toast.error(
error instanceof Error
? error.message
: "Failed to open artifact",
);
}
return;
}
try {
await openArtifactInNewTab({
filepath,
threadId,
isMock,
publicShareCode,
});
} catch (error) {
console.error(error);
toast.error(
error instanceof Error
? error.message
: "Failed to open artifact",
);
}
}}
/>
{isCodeFile && (
<ArtifactAction
icon={CopyIcon}
@@ -1,53 +1,10 @@
"use client";
import { Database } from "lucide-react";
import {
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import { useKnowledgeRagEnabled } from "@/core/settings";
import { cn } from "@/lib/utils";
import { Tooltip } from "./tooltip";
/**
* Sidebar toggle for knowledge-base RAG injection (phase 2).
*
* Session-wide and in-memory only: while enabled, every conversation sends
* ``context.knowledge_rag_enabled`` as ``runtime.context`` so
* ``KnowledgeRagMiddleware`` injects recalled knowledge before each model
* call. A page reload resets the switch to off.
*/
export function KnowledgeRagToggle({ className }: { className?: string }) {
const [enabled, setEnabled] = useKnowledgeRagEnabled();
return (
<SidebarMenuItem>
<Tooltip
content={
enabled
? "知识库召回:已开启(本次会话内所有对话默认开启)"
: "知识库召回:已关闭"
}
>
<SidebarMenuButton
isActive={enabled}
onClick={() => setEnabled(!enabled)}
className={className}
>
<Database />
<span>知识库召回</span>
<span
className={cn(
"ml-auto text-xs",
enabled ? "text-primary" : "text-muted-foreground/70",
)}
>
{enabled ? "已开启" : "已关闭"}
</span>
</SidebarMenuButton>
</Tooltip>
</SidebarMenuItem>
);
export function KnowledgeRagToggle({
className: _className,
}: {
className?: string;
}) {
void _className;
return null;
}
@@ -24,7 +24,6 @@ import {
History,
Layers,
Layout,
Library,
List,
ListChecks,
ListTodo,
@@ -287,12 +286,6 @@ export const sidebarMenuItems: MenuItem[] = [
icon: BookOpen,
path: "/page/workspace/knowledge",
},
{
id: "resource-knowledge-base",
label: "知识库资源管理",
icon: Library,
path: "/page/strategy/resource-management/resource-knowledge-base",
},
{
id: "resource-evaluation-index",
label: "作战资源管理",
@@ -131,17 +131,6 @@ export function AgentDetailSheet({
</section>
)}
{/* 人设(SOUL.md) */}
{agent.soul?.trim() && (
<section className="space-y-1.5">
<h4 className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
人设
</h4>
<pre className="whitespace-pre-wrap break-words rounded-md border border-border bg-muted/30 p-3 text-[12px] leading-relaxed text-foreground">
{agent.soul}
</pre>
</section>
)}
</>
)}
</div>
@@ -13,6 +13,7 @@ from __future__ import annotations
import json
import logging
import os
import hashlib
from datetime import datetime
from typing import Any
@@ -20,16 +21,13 @@ import httpx
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from deerflow.config import get_app_config
from deerflow.config.agents_config import load_agent_config
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/knowledge-ingest", tags=["knowledge-ingest"])
# 入库接口地址,可用环境变量覆盖(如切换 UAT/生产)。
KNOWES_INGEST_URL = os.environ.get(
"KNOWES_INGEST_URL",
"https://ch1.b.uat.4.cn/knowes/api/knowledge-outer/add",
)
_REQUEST_TIMEOUT_SECONDS = 30.0
# 标题字段在目标模板里可能叫「名称」或「标题」,按此顺序逐个尝试。
@@ -101,6 +99,21 @@ def _build_payload(template_name: str, batch_number: str, node_name: str, title_
}
def _build_node_name(title: str, timestamp: str) -> str:
"""Build a stable node name as md5(title) + timestamp + .md."""
title_md5 = hashlib.md5(title.encode("utf-8")).hexdigest()
return f"{title_md5}-{timestamp}.md"
def _resolve_ingest_url() -> str:
"""Resolve the external ingest URL from config.yaml, with env fallback."""
config_url = getattr(get_app_config().knowledge_ingest, "url", "").strip()
return os.environ.get(
"KNOWES_INGEST_URL",
config_url or "https://ch1.b.uat.4.cn/knowes/api/knowledge-outer/add",
)
@router.post("/publish", response_model=KnowledgeIngestPublishResponse, summary="Publish markdown artifact to external knowledge base")
async def publish_to_knowledge_base(request: KnowledgeIngestPublishRequest) -> KnowledgeIngestPublishResponse:
title = request.title.strip()
@@ -121,8 +134,10 @@ async def publish_to_knowledge_base(request: KnowledgeIngestPublishRequest) -> K
now = datetime.now()
# nodeName 是入库记录的唯一标识:md 标题 + 时间戳。两次尝试必须共用同一个
# nodeName,避免第一次实际已入库时第二次产生重复记录。
node_name = f"{title}-{now.strftime('%Y%m%d%H%M%S')}"
timestamp = now.strftime("%Y%m%d%H%M%S")
node_name = _build_node_name(title, timestamp)
batch_number = f"md{now.strftime('%Y%m%d')}"
ingest_url = _resolve_ingest_url()
logger.info(
"[knowledge-ingest] 开始入库: agent=%s template=%s nodeName=%s batchNumber=%s title=%r content_len=%d url=%s",
@@ -132,7 +147,7 @@ async def publish_to_knowledge_base(request: KnowledgeIngestPublishRequest) -> K
batch_number,
title,
len(content),
KNOWES_INGEST_URL,
ingest_url,
)
attempts: list[KnowledgeIngestAttempt] = []
@@ -148,7 +163,7 @@ async def publish_to_knowledge_base(request: KnowledgeIngestPublishRequest) -> K
json.dumps({**payload, "list": [{**payload["list"][0], "data": [{"fieldName": d["fieldName"], "value": d["value"][:200]} for d in payload["list"][0]["data"]]}]}, ensure_ascii=False),
)
try:
response = await client.post(KNOWES_INGEST_URL, json=payload, headers=headers)
response = await client.post(ingest_url, json=payload, headers=headers)
except httpx.HTTPError as exc:
logger.error("[knowledge-ingest] 第 %d 次发送请求异常 (标题字段=%s): %s", len(attempts) + 1, title_field, exc)
attempts.append(KnowledgeIngestAttempt(title_field=title_field, error=str(exc)))
@@ -15,9 +15,10 @@ from deerflow.config.checkpointer_config import CheckpointerConfig, load_checkpo
from deerflow.config.database_config import DatabaseConfig
from deerflow.config.extensions_config import ExtensionsConfig
from deerflow.config.gateway_config import GatewayConfig
from deerflow.config.guardrails_config import GuardrailsConfig, load_guardrails_config_from_dict
from deerflow.config.knowledge_config import KnowledgeConfig
from deerflow.config.log_system_config import LogSystemConfig
from deerflow.config.guardrails_config import GuardrailsConfig, load_guardrails_config_from_dict
from deerflow.config.knowledge_config import KnowledgeConfig
from deerflow.config.knowledge_ingest_config import KnowledgeIngestConfig
from deerflow.config.log_system_config import LogSystemConfig
from deerflow.config.login_config import AuthLoginConfig
from deerflow.config.memory_config import MemoryConfig, load_memory_config_from_dict
from deerflow.config.model_config import ModelConfig
@@ -135,8 +136,9 @@ class AppConfig(BaseModel):
run_events: RunEventsConfig = Field(default_factory=RunEventsConfig, description="Run event storage configuration")
checkpointer: CheckpointerConfig | None = Field(default=None, description="Checkpointer configuration")
stream_bridge: StreamBridgeConfig | None = Field(default=None, description="Stream bridge configuration")
browser_context: BrowserContextConfig = Field(default_factory=BrowserContextConfig, description="Optional browser page capture integration")
knowledge: KnowledgeConfig = Field(default_factory=KnowledgeConfig, description="Product knowledge base (obsidian-wiki compatible vault) configuration")
browser_context: BrowserContextConfig = Field(default_factory=BrowserContextConfig, description="Optional browser page capture integration")
knowledge: KnowledgeConfig = Field(default_factory=KnowledgeConfig, description="Product knowledge base (obsidian-wiki compatible vault) configuration")
knowledge_ingest: KnowledgeIngestConfig = Field(default_factory=KnowledgeIngestConfig, description="External knowledge-ingest endpoint configuration")
@classmethod
def resolve_config_path(cls, config_path: str | None = None) -> Path:
@@ -0,0 +1,12 @@
"""Configuration for the external knowledge-ingest endpoint."""
from pydantic import BaseModel, Field
class KnowledgeIngestConfig(BaseModel):
"""External knowledge-ingest configuration (`config.yaml` -> `knowledge_ingest`)."""
url: str = Field(
default="https://ch1.b.uat.4.cn/knowes/api/knowledge-outer/add",
description="External knowledge-ingest endpoint URL.",
)
@@ -32,7 +32,7 @@ class AgentRow(Base):
# ``is_private``. Empty string = "not assigned" → the listing layer treats
# it as the admin-configured default square. Each agent belongs to at most
# one square (single column), so it can never be published to two squares.
square_id: Mapped[str] = mapped_column(String(64), nullable=False, default="", index=True)
square_id: Mapped[str] = mapped_column(String(64), nullable=False, default="", server_default="", index=True)
# NULL = not featured on the homepage selector. Non-NULL = admin-curated
# featured rank (lower sorts first). Only ``published=True`` agents are
# eligible; the router enforces that invariant on writes. BIGINT so the
@@ -36,7 +36,7 @@ class SkillRow(Base):
# Publish square (发布广场) this skill is filed under once published. Pure
# organizational category, no access control. Empty string = unassigned →
# the listing layer resolves it to the default square. One square per skill.
square_id: Mapped[str] = mapped_column(String(64), nullable=False, default="", index=True)
square_id: Mapped[str] = mapped_column(String(64), nullable=False, default="", server_default="", index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(UTC))
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
+2
View File
@@ -108,6 +108,8 @@ vision:
model_name: deepseek-ai/DeepSeek-OCR
# prompt: 请详细描述这张图片的内容。
# max_tokens: 2048
knowledge_ingest:
url: "https://ch1.b.uat.4.cn/knowes/api/knowledge-outer/add"
# 上传文件识别配置。
# - auto_convert_documents: true 时,上传的 PDF / Word / Excel / PPT 会在服务端自动转换为
# Markdown(.md),agent 通过 read_file / grep 读取其内容 —— 即“识别”文档内容。
-1
View File
@@ -255,7 +255,6 @@ const menuItems: MenuItem[] = [
label: "网络行为体资源管理",
icon: Network,
},
{ id: "resource-knowledge-base", label: "知识库资源管理", icon: Library },
{ id: "resource-evaluation-index", label: "作战资源管理", icon: Library },
// {
// id: "resource-evaluation-index",