diff --git a/README.md b/README.md index 8f581d6..f49c287 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ An enterprise AI agent platform (智能体中台) built with Next.js. It lets organizations build and operate AI agent applications on top of their own knowledge and business processes — from RAG-powered knowledge bases and intelligent Q&A to visual workflow orchestration, process management, and multi-tenant administration. -![RAgent screenshot](docs/assets/screenshot01.png) +![RAgent screenshot](docs/assets/screenshot02.png) -Screenshot from v0.4.5 — predates the Digital Employee rename and the Skills section in the sidebar. +Dashboard, showing the Digital Employees and Skills sections. The greyed-out Prompt entry +and the version badge predate v0.5.0, which removed the prompt library. ## Core Features @@ -35,6 +36,13 @@ filesystem at runtime — and the UI covers authoring, review, binding, and per- variables it needs via a `.env.example` asset; each user fills in their own values, injected per execution. Values are never returned by any endpoint, including to administrators, who see only which keys are set. +- **Skills can be written by talking.** A built-in skill-creator ships with the platform. Bind + it to a digital employee and you can ask for a skill in plain language instead of starting + from a blank editor. It drafts the body, writes scripts, configures the sandbox and submits + for review — but approval stays a human action, and a draft's scripts are not in the + executable set until someone approves them. The built-in skill itself cannot be edited or + deleted by anyone, since it is versioned with the code and any change would be overwritten + on the next release. > ⚠️ **Deployment requirement.** Skill *execution* needs the `ragent-service` process to be > able to run `docker`. If the backend itself runs in a container, that container needs the @@ -44,9 +52,10 @@ filesystem at runtime — and the UI covers authoring, review, binding, and per- ## Agent prompts A digital employee's system prompt lives in its **Agent.md**, edited on the employee's own -page. The standalone prompt library is being retired and its navigation entry is disabled; -existing prompts were copied into the corresponding Agent.md verbatim, and the original -`prompt_id` is kept as a rollback anchor. +page. That is the only place it lives. + +The standalone prompt library was removed in v0.5.0. Existing prompts had already been copied +into the corresponding Agent.md verbatim, and nothing reads the old records at runtime. ## Architecture diff --git a/app/apps/[id]/page.tsx b/app/apps/[id]/page.tsx index b7b0f98..5ad09ca 100644 --- a/app/apps/[id]/page.tsx +++ b/app/apps/[id]/page.tsx @@ -91,8 +91,6 @@ interface AppInfo { /** 头像 URL:内置头像静态路径或上传后的 OSS 读代理路径;空=按名称生成占位 */ avatar_url?: string | null; agent_md?: string | null; - /** legacy 提示词绑定;用来判断 Agent.md 区块该说「升级」还是「创建」 */ - prompt_id?: number | null; created_at: string; updated_at: string; settings?: Record; @@ -586,7 +584,6 @@ export default function AppDetailPage({ params }: { params: Promise<{ id: string appId={appId} ownerUserId={appInfo.user_id} platform={appInfo.platform} - promptId={appInfo.prompt_id} onChanged={loadAppInfo} /> diff --git a/app/apps/components/AgentMdEditor.tsx b/app/apps/components/AgentMdEditor.tsx index 2015817..b3990c5 100644 --- a/app/apps/components/AgentMdEditor.tsx +++ b/app/apps/components/AgentMdEditor.tsx @@ -31,11 +31,6 @@ interface AgentMdEditorProps { /** app 的创建者。后端的 agent-md 写入一直是 owner 或超管,UI 此前只放超管 */ ownerUserId?: number | null; platform: string; - /** - * 应用绑定的 legacy prompt。没有它就没有"升级"可言——文案要说「创建」, - * 否则用户看到「升级为 Agent.md」会以为有东西要迁移(2026-07-31 报的 bug 现场)。 - */ - promptId?: number | null; /** Agent.md 状态变化(升级 / 回退)后通知父组件刷新应用信息 */ onChanged?: () => void; } @@ -51,7 +46,6 @@ export default function AgentMdEditor({ appId, ownerUserId, platform, - promptId, onChanged, }: AgentMdEditorProps) { const t = useTranslations("skills"); @@ -73,7 +67,6 @@ export default function AgentMdEditor({ const isWechat = platform === "Wechat"; const isLegacy = agentMd?.is_legacy !== false; // 有旧提示词 = 真的在"升级";没有 = 从零"创建" - const hasLegacyPrompt = promptId != null; // 后端内容加载/变化时回填(本地有未保存修改则不覆盖) useEffect(() => { @@ -145,8 +138,7 @@ export default function AgentMdEditor({ {/* 标题直接写「角色设定」,不再显示文件名 Agent.md,也不再挂状态徽章: 标题旁边写着「角色设定」、下面又挂一个写着「角色设定」的彩色 Badge, - 是同一句话说两遍。isLegacy 的状态由下面那段说明文字承担 - (legacyDesc / noAgentMdDesc 已经把处境讲清楚了)。 + 是同一句话说两遍。状态由下面那段说明文字承担(noAgentMdDesc)。 */} @@ -179,7 +171,7 @@ export default function AgentMdEditor({ {isLegacy ? (

- {hasLegacyPrompt ? t("legacyDesc") : t("noAgentMdDesc")} + {t("noAgentMdDesc")}

{canManage && ( <> @@ -189,7 +181,7 @@ export default function AgentMdEditor({ ) : ( )} - {hasLegacyPrompt ? t("upgradeToAgentMd") : t("createAgentMd")} + {t("createAgentMd")} {isWechat && (

{t("wechatUpgradeDisabled")}

diff --git a/app/apps/page.tsx b/app/apps/page.tsx index 317a7ef..58c36a6 100644 --- a/app/apps/page.tsx +++ b/app/apps/page.tsx @@ -78,7 +78,6 @@ interface App { avatar_url?: string | null; user_id: number; ai_model: string; - prompt_id: number | null; agent_md?: string | null; dataset_ids: string[]; tool_count?: number; @@ -189,8 +188,7 @@ export default function AppsPage() { // 空串 = 用户主动清空(后端据此落 NULL);null = 本来就没设过 avatar_url: string | null; ai_model: string; - prompt_id: number | null; - dataset_ids: string[]; + dataset_ids: string[]; email?: string; settings: Record; is_default?: boolean; @@ -201,7 +199,6 @@ export default function AppsPage() { platform: "Web", avatar_url: null, ai_model: "deepseek", - prompt_id: null, dataset_ids: [], email: "", settings: {}, @@ -320,8 +317,7 @@ export default function AppsPage() { platform: "Web", avatar_url: null, ai_model: "deepseek", - prompt_id: null, - dataset_ids: [], + dataset_ids: [], email: "", settings: {}, is_default: false, @@ -349,8 +345,7 @@ export default function AppsPage() { app_type: "Chat" as const, platform: "Web" as const, ai_model: "deepseek", - prompt_id: null, - dataset_ids: ["quality_knowledge_base"], // 使用质量知识库 + dataset_ids: ["quality_knowledge_base"], // 使用质量知识库 settings: { workflow: { nodes: [ @@ -465,7 +460,6 @@ export default function AppsPage() { platform: app.platform, avatar_url: app.avatar_url ?? null, ai_model: app.ai_model, - prompt_id: app.prompt_id, dataset_ids: datasetIds, email: app.email || "", settings: app.settings || {}, diff --git a/app/components/AppSidebar.tsx b/app/components/AppSidebar.tsx index bb56491..ec1be3e 100644 --- a/app/components/AppSidebar.tsx +++ b/app/components/AppSidebar.tsx @@ -136,17 +136,6 @@ export default function AppSidebar() { visible: true, items: [ { title: t("chatSessions"), icon: MessageSquareMore, path: "/chat-sessions" }, - { - // 提示词管理即将下线:应用的提示词真源已迁到 Agent.md(apps.agent_md), - // chat 与企微两条链都优先读它(app/utils/app_prompt.resolve_app_prompt_body)。 - // 入口先置灰而不是直接删——`prompt_id` 仍保留作回滚锚点,真删要等确认没有 - // 应用还只靠它。 - title: t("promptManagement"), - icon: MessageSquare, - path: "/prompts", - visible: isSuperAdmin || isTenantAdmin, - deprecated: t("promptManagementDeprecated"), - }, { // P5 开放自建:普通用户也可创建自己的 Skill(草稿走提交审核) title: t("skillsManagement"), diff --git a/app/components/DynamicTitle.tsx b/app/components/DynamicTitle.tsx index 34d5347..287d7f3 100644 --- a/app/components/DynamicTitle.tsx +++ b/app/components/DynamicTitle.tsx @@ -13,7 +13,6 @@ const routeTitleKeys: { [key: string]: string } = { "/graph": "knowledgeGraph", "/process-management": "processManagement", "/sop": "sopManagement", - "/prompts": "promptManagement", "/organization": "organization", "/user": "userManagement", "/settings": "settings", @@ -23,14 +22,10 @@ const routeTitleKeys: { [key: string]: string } = { // 子路由标题映射 const subRouteTitleKeys: { [key: string]: string } = { - "/prompts/new": "newPrompt", - "/prompts/": "promptDetail", - "/monitoring/prompts": "promptMonitoring", }; // 动态路由模式匹配 const dynamicRoutePatterns: { pattern: RegExp; titleKey: string }[] = [ - { pattern: /^\/prompts\/[^/]+$/, titleKey: "promptDetail" }, ]; export default function DynamicTitle() { @@ -75,7 +70,7 @@ export default function DynamicTitle() { - v0.4.5 + v0.5.0
diff --git a/app/prompts/[id]/page.tsx b/app/prompts/[id]/page.tsx deleted file mode 100644 index b0a2608..0000000 --- a/app/prompts/[id]/page.tsx +++ /dev/null @@ -1,260 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { useRouter, useParams } from "next/navigation"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Checkbox } from "@/components/ui/checkbox"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Badge } from "@/components/ui/badge"; -import { Separator } from "@/components/ui/separator"; -import axios from "@/lib/axios"; -import { useCurrentUser } from "@/hooks/useCurrentUser"; -import { toast } from "sonner"; -import { useTranslations } from "next-intl"; - -interface Prompt { - id: number; - role: string; - content: string; - visibility: "private" | "dept" | "tenant" | "public"; - is_default: boolean; - is_active: boolean; - user_id: number; - created_at: string; - updated_at: string; -} - -export default function EditPromptPage() { - const router = useRouter(); - const params = useParams(); - const promptId = params?.id as string; - const { user, loading: userLoading } = useCurrentUser(); - const t = useTranslations("prompts"); - const tc = useTranslations("common"); - - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [prompt, setPrompt] = useState(null); - const [formData, setFormData] = useState({ - role: "", - content: "", - visibility: "dept" as "private" | "dept" | "tenant" | "public", - is_default: false, - is_active: true, - }); - - useEffect(() => { - if (!user) return; - fetchPrompt(); - }, [user, promptId]); - - const fetchPrompt = async () => { - try { - setLoading(true); - const response = await axios.get(`/api/prompts`); - const prompts = response.data; - const currentPrompt = prompts.find((p: Prompt) => p.id.toString() === promptId); - - if (!currentPrompt) { - toast.error(t("promptNotFound")); - router.push("/prompts"); - return; - } - - // 检查权限:后端API会进行权限验证,这里不需要重复检查 - setPrompt(currentPrompt); - setFormData({ - role: currentPrompt.role, - content: currentPrompt.content, - visibility: currentPrompt.visibility, - is_default: currentPrompt.is_default, - is_active: currentPrompt.is_active, - }); - } catch (error: any) { - console.error("获取提示词失败:", error); - if (error.response?.status === 403) { - toast.error(t("noPermission")); - router.push("/prompts"); - } else { - toast.error(t("fetchFailed")); - router.push("/prompts"); - } - } finally { - setLoading(false); - } - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!formData.role.trim()) { - alert(t("roleRequired")); - return; - } - - if (!formData.content.trim()) { - alert(t("contentRequired")); - return; - } - - try { - setSaving(true); - await axios.put(`/api/prompts/${promptId}`, formData); - toast.success(t("saveSuccess")); - router.push("/prompts"); - } catch (error: any) { - console.error("保存失败:", error); - if (error.response?.status === 403) { - toast.error(t("noEditPermission")); - router.push("/prompts"); - } else { - toast.error(error.response?.data?.error || t("saveFailed")); - } - } finally { - setSaving(false); - } - }; - - const handleCancel = () => { - router.push("/prompts"); - }; - - // 显示加载状态 - if (loading) { - return ( -
-
{tc("loading")}
-
- ); - } - - if (!user) { - return ( -
-
{t("cannotLoadUser")}
-
- ); - } - - if (!prompt) { - return ( -
-
{t("promptNotFound")}
-
- ); - } - - return ( -
-
-
- - -
-
- - - - {t("promptInfo")} - - -
-
-
- - setFormData({ ...formData, role: e.target.value })} - placeholder={t("roleNamePlaceholder")} - required - /> -
- -
- - -
-
- -
- -