diff --git a/app/apps/[id]/page.tsx b/app/apps/[id]/page.tsx
index ba83046..82413e9 100644
--- a/app/apps/[id]/page.tsx
+++ b/app/apps/[id]/page.tsx
@@ -2,6 +2,7 @@
import { use, useState, useEffect } from "react";
import { useTranslations } from "next-intl";
+import { triggerLabel } from "@/lib/appTrigger";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@@ -49,6 +50,7 @@ import { useInvalidateAppSkillDiagnostics } from "@/hooks/useAppSkillDiagnostics
import AppSkillsSection from "../components/AppSkillsSection";
import AppSkillDiagnostics from "../components/AppSkillDiagnostics";
import AgentMdEditor from "../components/AgentMdEditor";
+import AppAvatar from "../components/AppAvatar";
import ReviewLogDialog from "@/components/ReviewLogDialog";
import ReviewRejectDialog from "@/components/ReviewRejectDialog";
import { useCurrentUser } from "@/hooks/useCurrentUser";
@@ -85,6 +87,8 @@ interface AppInfo {
app_type: string;
platform: string;
ai_model: string;
+ /** 头像 URL:内置头像静态路径或上传后的 OSS 读代理路径;空=按名称生成占位 */
+ avatar_url?: string | null;
agent_md?: string | null;
/** legacy 提示词绑定;用来判断 Agent.md 区块该说「升级」还是「创建」 */
prompt_id?: number | null;
@@ -96,6 +100,8 @@ interface AppInfo {
visibility?: string;
/** 创建者(提交人)用户ID,自审判定用 */
user_id?: number | null;
+ /** 作者显示名(nickname 优先退 username)。用户被删除时为空 */
+ author?: string | null;
owner_dept_id?: number | null;
owner_tenant_id?: number | null;
}
@@ -245,10 +251,10 @@ export default function AppDetailPage({ params }: { params: Promise<{ id: string
// 审核人不能审自己提交的对象(超管除外,后端违者 403)
const selfReview = isSelfReview(user?.id, appInfo.user_id, checkSuperAdmin(user));
// 状态徽标并入基本信息首行;动作/提示另起一行,且只在真有内容时渲染
+ // 提交审核按钮已移到标题右侧,草稿态下这条 footer 便没有内容了——留着就是一条
+ // 空的分隔线。rejected 仍要留:那里有"查看驳回理由"的链接,塞不进 hover。
const reviewFooterVisible =
- status === "draft" ||
- status === "rejected" ||
- (status === "pending_review" && canReview);
+ status === "rejected" || (status === "pending_review" && canReview);
return (
{t("back")}
+
{appInfo.name}
@@ -281,12 +292,31 @@ export default function AppDetailPage({ params }: { params: Promise<{ id: string
{appInfo.app_type !== "Custom" && (
- {t("basicInfo")}
+ {/* 动作按钮放标题右侧,与「已绑定工具」「已绑定 Skill」两区一致。
+ 原先它单独占一行、下面还跟一句说明,把一屏里最靠上的位置让给了
+ 一个大多数时候不需要点的按钮;说明文字改挂到按钮的 hover 上。 */}
+
+ {t("basicInfo")}
+ {(status === "draft" || status === "rejected") && (
+
+ )}
+
-
+ {/* 6 列:触发方式 / 平台 / AI 模型 / 作者 / 创建时间 / 状态。
+ 加了作者之后是 6 格,留在 5 列会让最后一格独占一行。 */}
+
{t("appType")}
-
{appInfo.app_type}
+
{triggerLabel(appInfo.app_type, t)}
{t("platform")}
@@ -296,6 +326,12 @@ export default function AppDetailPage({ params }: { params: Promise<{ id: string
{t("aiModel")}
{appInfo.ai_model}
+ {/* 作者。用户被删除时后端给 null——显示 — 而不是整格消失:
+ 这是固定列的网格,少一格会让后面的列错位。 */}
+
+
{t("author")}
+
{appInfo.author || "—"}
+
{tc("createdAt")}
{formatDate(appInfo.created_at)}
@@ -310,19 +346,8 @@ export default function AppDetailPage({ params }: { params: Promise<{ id: string
)}
{/* 审核动作与状态提示:仅在确有内容时才占一行,已发布应用不留空带 */}
{reviewFooterVisible && (
-
+
- {(status === "draft" || status === "rejected") && (
-
- )}
{status === "pending_review" &&
canReview &&
(selfReview ? (
@@ -361,9 +386,6 @@ export default function AppDetailPage({ params }: { params: Promise<{ id: string
)}
- {status === "draft" && (
-
{tr("draftOwnerOnlyHint")}
- )}
)}
@@ -392,8 +414,13 @@ export default function AppDetailPage({ params }: { params: Promise<{ id: string
{/* 以下工具统计 / 绑定列表仅非 Custom 应用展示 */}
{appInfo.app_type !== "Custom" && (
<>
- {/* 工具统计 */}
- {statistics && (
+ {/*
+ 工具统计。没绑工具就整行不显示——三个 0 既不提供信息,还占掉一屏顶部
+ 最显眼的位置。这么判是安全的:后端的统计就是遍历 app_tools 绑定关系算
+ 出来的(tools.py get_app_tools_statistics),零绑定时三个数必然都是 0,
+ 不存在"有历史调用但被藏起来"的情况。
+ */}
+ {statistics && appTools.length > 0 && (
diff --git a/app/apps/components/AgentMdEditor.tsx b/app/apps/components/AgentMdEditor.tsx
index 8cbb63c..0ec9191 100644
--- a/app/apps/components/AgentMdEditor.tsx
+++ b/app/apps/components/AgentMdEditor.tsx
@@ -2,7 +2,6 @@
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
-import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Textarea } from "@/components/ui/textarea";
@@ -13,16 +12,6 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@/components/ui/alert-dialog";
import {
AlertCircle,
AlertTriangle,
@@ -30,7 +19,6 @@ import {
FileText,
Loader2,
Sparkles,
- Undo2,
X,
} from "lucide-react";
import { useAgentMd, type AgentMdValidationError } from "@/hooks/useAgentMd";
@@ -62,7 +50,7 @@ export default function AgentMdEditor({ appId, platform, promptId, onChanged }:
const { user } = useCurrentUser();
const canManage = checkSuperAdmin(user);
- const { agentMd, loading, save, generate, remove, fetchExport } = useAgentMd(appId);
+ const { agentMd, loading, save, generate, fetchExport } = useAgentMd(appId);
const [content, setContent] = useState("");
const [dirty, setDirty] = useState(false);
@@ -72,7 +60,6 @@ export default function AgentMdEditor({ appId, platform, promptId, onChanged }:
const [saveWarnings, setSaveWarnings] = useState([]);
const [exportContent, setExportContent] = useState(null);
const [exportLoading, setExportLoading] = useState(false);
- const [revertDialogOpen, setRevertDialogOpen] = useState(false);
const isWechat = platform === "Wechat";
const isLegacy = agentMd?.is_legacy !== false;
@@ -114,17 +101,6 @@ export default function AgentMdEditor({ appId, platform, promptId, onChanged }:
}
};
- const handleRevert = async () => {
- const ok = await remove();
- if (ok) {
- setRevertDialogOpen(false);
- setDirty(false);
- setContent("");
- setValidationErrors([]);
- setSaveWarnings([]);
- onChanged?.();
- }
- };
const handleExportView = async () => {
setExportLoading(true);
@@ -157,14 +133,15 @@ export default function AgentMdEditor({ appId, platform, promptId, onChanged }:
+ {/*
+ 标题直接写「角色设定」,不再显示文件名 Agent.md,也不再挂状态徽章:
+ 标题旁边写着「角色设定」、下面又挂一个写着「角色设定」的彩色 Badge,
+ 是同一句话说两遍。isLegacy 的状态由下面那段说明文字承担
+ (legacyDesc / noAgentMdDesc 已经把处境讲清楚了)。
+ */}
- Agent.md
- {isLegacy ? (
- {t("legacyMode")}
- ) : (
- {t("agentMdMode")}
- )}
+ {t("agentMdMode")}
{!isLegacy && canManage && (
@@ -181,15 +158,6 @@ export default function AgentMdEditor({ appId, platform, promptId, onChanged }:
)}
{t("exportView")}
-