feat(roundtable): 多智能体会商 + 业务链条页脱敏
- 区域级脱敏 Hook useDesensitizeSubtree:对左右侧面板、顶部标题、 业务链条选择器/列表弹窗/列表页/配置页/映射页整片脱敏(文本节点, 跳过 input/textarea 等可编辑控件) - 消息列表正文保持原文,仅智能体名称脱敏(senderName / 席位名 / 流程图节点) - Step3 总结/报告/大屏、推荐弹窗等结果面板脱敏 - 沙箱产物预览不脱敏:ArtifactFileDetail 加 disableDesensitize,会商沙箱传入 - desensitize.ts 新增 desensitizeDeep 递归脱敏结构化数据 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0e4caa5a66
commit
45049975c2
29 files changed
+853
-66
No files matched your search
@@ -31,15 +31,23 @@ import { textOfMessage } from "@/core/threads/utils";
|
||||
import { env } from "@/env";
|
||||
import { desensitize } from "@/lib/desensitize";
|
||||
|
||||
export default function AgentChatPage() {
|
||||
export default function AgentChatPage({
|
||||
embeddedAgentId,
|
||||
}: {
|
||||
/** 当作为「通用问答右侧嵌入」渲染时由父组件传入的 agent id(替代路由参数)。
|
||||
* 传入即视为嵌入态:路由仍停留在 /chats/new,仅右侧问答区换成该智能体的对话。 */
|
||||
embeddedAgentId?: string;
|
||||
} = {}) {
|
||||
const routeBase = useRouteBase();
|
||||
const { t } = useI18n();
|
||||
const [showFollowups, setShowFollowups] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const { agent_id } = useParams<{
|
||||
const { agent_id: agentIdFromPath } = useParams<{
|
||||
agent_id: string;
|
||||
}>();
|
||||
const agent_id = embeddedAgentId ?? agentIdFromPath;
|
||||
const isEmbedded = !!embeddedAgentId;
|
||||
|
||||
// If ChatPage stashed an opening message for this agent, skip the landing
|
||||
// screen on first render so the user doesn't see a flash of the welcome UI
|
||||
@@ -208,7 +216,13 @@ export default function AgentChatPage() {
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
router.push(`${routeBase}/agents/${agent_id}/chats/new`);
|
||||
// 嵌入态:保持停留在通用问答右侧,开同一智能体的新对话;
|
||||
// 非嵌入态:走该智能体的独立新对话页(按当前路由 base)。
|
||||
router.push(
|
||||
isEmbedded
|
||||
? `/page/workspace/chats/new?agent=${agent_id}`
|
||||
: `${routeBase}/agents/${agent_id}/chats/new`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<PlusSquare /> {t.agents.newChat}
|
||||
|
||||
@@ -31,10 +31,13 @@ import {
|
||||
type BusinessMapping,
|
||||
} from "@/strategy-components/api/business-mapping";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useDesensitizeSubtree } from "@/roundtable-planning/lib/use-desensitize-subtree";
|
||||
|
||||
export default function BusinessChainMappingPage() {
|
||||
const { theme } = useTheme();
|
||||
const isDark = theme === "dark";
|
||||
// 整片脱敏:业务链条映射页展示文本(链条名/描述/席位名/标题)。输入框 Hook 自动跳过。
|
||||
const dzRef = useDesensitizeSubtree<HTMLDivElement>();
|
||||
|
||||
const [rows, setRows] = useState<BusinessMapping[]>([]);
|
||||
const [agents, setAgents] = useState<Agent[]>([]);
|
||||
@@ -160,7 +163,7 @@ export default function BusinessChainMappingPage() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={cn(shellClass, "[&_.t-input]:rounded-[5px] [&_.t-input__wrap]:rounded-[5px]")}>
|
||||
<div ref={dzRef} className={cn(shellClass, "[&_.t-input]:rounded-[5px] [&_.t-input__wrap]:rounded-[5px]")}>
|
||||
<div className="mx-auto flex min-h-full max-w-7xl flex-col gap-6 px-6 py-7 lg:px-8">
|
||||
|
||||
{/* ── 页头 banner ── */}
|
||||
|
||||
@@ -34,7 +34,7 @@ import { isChatsIframeWorkspacePath } from "@/core/page-layout/chats-iframe-rout
|
||||
import { useSidebar } from "@/components/ui/sidebar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PanelLeftOpen } from "lucide-react";
|
||||
import { useAgentSelector } from "@/core/agents";
|
||||
import { useAgentSelector, useAgentsPaginated } from "@/core/agents";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useModels } from "@/core/models/hooks";
|
||||
import { useNotification } from "@/core/notification/hooks";
|
||||
@@ -130,6 +130,10 @@ export default function ChatPage() {
|
||||
const browserContextAutoSubmitRef = useRef<string | null>(null);
|
||||
const { agents: selectorAgents, isLoading: selectorLoading } =
|
||||
useAgentSelector();
|
||||
// 已选 agent 的存在性校验需要更宽的来源:侧边栏可选未配置进管理弹窗的 agent
|
||||
// (用户自己的智能体),这些也算有效,故把「我的 / 广场」一并纳入校验集(缓存共享)。
|
||||
const { agents: mineAgents } = useAgentsPaginated({ scope: "mine", page: 1, pageSize: 20 });
|
||||
const { agents: squareAgents } = useAgentsPaginated({ scope: "square", page: 1, pageSize: 20 });
|
||||
const { selectedArtifact } = useArtifacts();
|
||||
useSpecificChatMode();
|
||||
|
||||
@@ -142,7 +146,7 @@ export default function ChatPage() {
|
||||
setHasStartedConversation(false);
|
||||
setPromptPrefixOn(false);
|
||||
// Pre-select the agent the user last started a conversation with so a
|
||||
// fresh chat defaults to it. Validated against the selector list below.
|
||||
// fresh chat defaults to it. Validated against the known agent list below.
|
||||
setSelectedAgentId(getLastAgentId());
|
||||
}
|
||||
}, [isNewThread, threadId]);
|
||||
@@ -152,10 +156,14 @@ export default function ChatPage() {
|
||||
// dead agent. Wait for the list to load before deciding.
|
||||
useEffect(() => {
|
||||
if (selectorLoading || !selectedAgentId) return;
|
||||
if (!selectorAgents.some((a) => a.id === selectedAgentId)) {
|
||||
// 校验集 = 管理弹窗 ∪ 我的 ∪ 广场;任一存在即视为有效(含用户未配置进管理弹窗
|
||||
// 的自有智能体)。三者都还没加载出来时不做清除,避免把恢复值误删。
|
||||
const known = [...selectorAgents, ...mineAgents, ...squareAgents];
|
||||
if (known.length === 0) return;
|
||||
if (!known.some((a) => a.id === selectedAgentId)) {
|
||||
setSelectedAgentId(null);
|
||||
}
|
||||
}, [selectedAgentId, selectorAgents, selectorLoading]);
|
||||
}, [selectedAgentId, selectorAgents, mineAgents, squareAgents, selectorLoading]);
|
||||
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
|
||||
@@ -145,6 +145,21 @@ export function ChatRuntime({ children }: { children: ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用问答新对话内容区:默认渲染 ChatPage;当 URL 带 ``?agent=<id>``(点击左栏
|
||||
* 「常用智能体」时由侧边栏写入)时,**在右侧问答区原地嵌入**该智能体的对话页
|
||||
* (AgentChatPage),路由仍停留在 /chats/new,不跳转出去、不更换左侧栏。
|
||||
* key 绑定 location.key —— 每次点击(即便同一 agent)都重开一个干净的会话。
|
||||
*/
|
||||
function NewChatContent() {
|
||||
const location = useLocation();
|
||||
const agentId = new URLSearchParams(location.search).get('agent');
|
||||
if (agentId) {
|
||||
return <AgentChatPage key={`${agentId}:${location.key}`} embeddedAgentId={agentId} />;
|
||||
}
|
||||
return <ChatPage />;
|
||||
}
|
||||
|
||||
function NewChatEntryRoute({
|
||||
layoutProps,
|
||||
}: {
|
||||
@@ -163,7 +178,7 @@ function NewChatEntryRoute({
|
||||
return (
|
||||
<WorkspaceLayout {...layoutProps} defaultHistorySidebarOpen>
|
||||
<ChatRuntime>
|
||||
<ChatPage />
|
||||
<NewChatContent />
|
||||
</ChatRuntime>
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user