敏感词

This commit is contained in:
fengxiangdebaozi committed 2026-05-25 18:20:40 +08:00
1 parent 6396ccc701
commit f472bba9f0
38 files changed
+540 -173

No files matched your search

+12 -49
View File
@@ -400,63 +400,26 @@ export function PageSidebar({
onOpenChange={setNotificationOpen}
/>
<aside
className={`fixed left-0 top-0 z-40 flex h-screen flex-col overflow-hidden shadow-sm theme-transition transition-all duration-300 ${
className={`fixed left-0 top-16 z-40 flex h-[calc(100vh-4rem)] flex-col overflow-hidden shadow-sm theme-transition transition-all duration-300 ${
isDark
? "border-r border-blue-800/30 bg-slate-900"
: "border-r border-red-100 bg-white"
} ${collapsed ? "w-16" : "w-64"}`}
>
<div
className={`flex h-16 items-center theme-transition ${
isDark ? "border-b border-blue-800/30" : "border-b border-red-100"
} ${collapsed ? "justify-center px-2" : "justify-between px-6"}`}
<button
type="button"
onClick={onToggleCollapsed}
title={collapsed ? "展开侧边栏" : "收起侧边栏"}
className={`absolute -right-3 top-6 z-50 flex h-6 w-6 items-center justify-center rounded-full shadow-md theme-transition ${
isDark
? "bg-slate-700 text-slate-300 hover:bg-slate-600"
: "bg-white text-gray-500 hover:bg-red-50 hover:text-red-600 border border-red-100"
}`}
>
<div className="flex min-w-0 items-center gap-3">
<div
className={`flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg transition-transform duration-300 ease-out ${
isDark
? "bg-gradient-to-br from-blue-500 to-cyan-400"
: "bg-gradient-to-br from-red-500 to-red-400"
}`}
>
<FileText className="h-4 w-4 text-white" />
</div>
{!collapsed && (
<div className="overflow-hidden whitespace-nowrap animate-in fade-in-0 slide-in-from-left-2 duration-300">
<h1
className={`text-sm font-semibold theme-transition ${
isDark ? "text-white" : "text-gray-900"
}`}
>
CM助手
</h1>
</div>
)}
</div>
{!collapsed && (
<button
type="button"
onClick={onToggleCollapsed}
className={iconButtonClass}
title="Collapse sidebar"
>
<ChevronLeft className="h-4 w-4" />
</button>
)}
</div>
{collapsed ? <ChevronRight className="h-3 w-3" /> : <ChevronLeft className="h-3 w-3" />}
</button>
<nav className={`flex-1 overflow-y-auto ${collapsed ? "p-2" : "p-4"} space-y-2 transition-all duration-300 ease-out`}>
{collapsed && (
<button
type="button"
onClick={onToggleCollapsed}
className={`flex w-full items-center justify-center ${iconButtonClass}`}
title="Expand sidebar"
>
<ChevronRight className="h-5 w-5" />
</button>
)}
{visibleMenuItems.map((item) =>
item.children ? renderBranch(item) : renderLeaf(item),
)}
@@ -3,6 +3,7 @@
import { BotIcon } from "lucide-react";
import { type Agent } from "@/core/agents";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
export function AgentWelcome({
@@ -14,8 +15,10 @@ export function AgentWelcome({
agent: Agent | null | undefined;
agentName: string;
}) {
const displayName = agent?.name ?? agentName;
const description = agent?.description;
const displayName = desensitize(agent?.name ?? agentName);
const description = agent?.description
? desensitize(agent.description)
: undefined;
return (
<div
@@ -48,6 +48,7 @@ import type { Agent } from "@/core/agents";
import { getStoredAuth } from "@/core/auth";
import { useI18n } from "@/core/i18n/hooks";
import { useSkills } from "@/core/skills/hooks";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
interface AgentCardProps {
@@ -195,12 +196,12 @@ export function AgentCard({ agent }: AgentCardProps) {
<BotIcon className="h-5 w-5" />
</div>
<div className="min-w-0 flex-1">
<CardTitle className="truncate text-base" title={agent.name}>
{agent.name}
<CardTitle className="truncate text-base" title={desensitize(agent.name)}>
{desensitize(agent.name)}
</CardTitle>
{agent.model && (
<Badge variant="secondary" className="mt-0.5 text-xs">
{agent.model}
{desensitize(agent.model)}
</Badge>
)}
</div>
@@ -252,7 +253,7 @@ export function AgentCard({ agent }: AgentCardProps) {
</div>
{agent.description && (
<CardDescription className="mt-2 line-clamp-2 text-sm">
{agent.description}
{desensitize(agent.description)}
</CardDescription>
)}
</CardHeader>
@@ -272,7 +273,7 @@ export function AgentCard({ agent }: AgentCardProps) {
variant="outline"
className="text-xs"
>
{group}
{desensitize(group)}
</Badge>
))}
{agent.skills?.map((skill) => (
@@ -286,7 +287,7 @@ export function AgentCard({ agent }: AgentCardProps) {
setDetailSkillName(skill);
}}
>
{skill}
{desensitize(skill)}
</Badge>
))}
</div>
@@ -377,7 +378,7 @@ export function AgentCard({ agent }: AgentCardProps) {
<Dialog open={editOpen} onOpenChange={setEditOpen}>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-2xl">
<DialogHeader>
<DialogTitle>编辑智能体:{agent.name}</DialogTitle>
<DialogTitle>编辑智能体:{desensitize(agent.name)}</DialogTitle>
<DialogDescription>
可以修改名称、描述、技能白名单、SOUL 内容与发布状态。
</DialogDescription>
@@ -428,10 +429,10 @@ export function AgentCard({ agent }: AgentCardProps) {
size="sm"
variant={checked ? "default" : "outline"}
onClick={() => toggleSkill(skill.name)}
title={skill.description}
title={desensitize(skill.description ?? "")}
className="h-8 max-w-[200px] rounded-r-none"
>
<span className="truncate">{skill.name}</span>
<span className="truncate">{desensitize(skill.name)}</span>
</Button>
<Button
type="button"
@@ -50,6 +50,7 @@ import { installSkill } from "@/core/skills/api";
import { streamdownPlugins } from "@/core/streamdown";
import { checkCodeFile, getFileName } from "@/core/utils/files";
import { env } from "@/env";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
import { ArtifactLink } from "../citations/artifact-link";
@@ -608,7 +609,7 @@ export function ArtifactFileDetail({
viewMode === "preview" &&
(language === "markdown" || language === "html") && (
<ArtifactFilePreview
content={displayContent}
content={desensitize(displayContent ?? "")}
language={language ?? "text"}
animated={showPreviewSwitchPulse}
activeCursor={isApplyingEdit}
@@ -618,7 +619,11 @@ export function ArtifactFileDetail({
<div className="size-full">
<CodeEditor
className="size-full resize-none rounded-none border-none bg-transparent"
value={displayContent ?? ""}
value={
canEditArtifact
? (displayContent ?? "")
: desensitize(displayContent ?? "")
}
readonly={!canEditArtifact}
onChange={
canEditArtifact
@@ -8,6 +8,7 @@ import {
type MessageResponseProps,
} from "@/components/ai-elements/message";
import { streamdownPlugins } from "@/core/streamdown";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
import { CitationLink } from "../citations/citation-link";
@@ -63,6 +64,8 @@ export function MarkdownContent({
if (!content) return null;
const displayContent = desensitize(content);
return (
<MessageResponse
className={className}
@@ -70,7 +73,7 @@ export function MarkdownContent({
rehypePlugins={rehypePlugins}
components={components}
>
{content}
{displayContent}
</MessageResponse>
);
}
@@ -37,6 +37,7 @@ import {
} from "@/core/messages/utils";
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
import { humanMessagePlugins } from "@/core/streamdown";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
import { CopyButton } from "../copy-button";
@@ -226,7 +227,7 @@ function MessageContent_({
<TaskTrigger title="">
<div className="text-muted-foreground flex w-full cursor-default items-center gap-2 text-sm select-none">
<Loader className="size-4" />
<span>{contentToDisplay}</span>
<span>{desensitize(contentToDisplay)}</span>
</div>
</TaskTrigger>
</Task>
@@ -240,7 +241,7 @@ function MessageContent_({
<AIElementMessageContent className={className}>
<Reasoning isStreaming={isLoading}>
<ReasoningTrigger />
<ReasoningContent>{reasoningContent}</ReasoningContent>
<ReasoningContent>{desensitize(reasoningContent)}</ReasoningContent>
</Reasoning>
<MessageTokenUsage
enabled={tokenUsageEnabled}
@@ -259,7 +260,7 @@ function MessageContent_({
components={components}
parseIncompleteMarkdown={false}
>
{contentToDisplay}
{desensitize(contentToDisplay)}
</AIElementMessageResponse>
) : null;
return (
@@ -58,6 +58,7 @@ import {
import type { AgentThread, AgentThreadState } from "@/core/threads/types";
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
import { env } from "@/env";
import { desensitize } from "@/lib/desensitize";
import { isIMEComposing } from "@/lib/ime";
export function RecentChatList() {
@@ -211,7 +212,7 @@ export function RecentChatList() {
className="block w-full whitespace-nowrap group-hover/side-menu-item:overflow-hidden"
href={pathOfThread(thread)}
>
{titleOfThread(thread)}
{desensitize(titleOfThread(thread))}
</Link>
</SidebarMenuButton>
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true" && (
@@ -42,6 +42,7 @@ import type {
MemoryTarget,
MemoryV2Config,
} from "@/core/memory/types";
import { desensitize } from "@/lib/desensitize";
import { SettingsSection } from "./settings-section";
@@ -173,7 +174,7 @@ export function MemorySettingsPage() {
className="flex items-start justify-between gap-3 rounded-md border p-3"
>
<p className="min-w-0 flex-1 whitespace-pre-wrap text-sm [overflow-wrap:anywhere]">
{entry}
{desensitize(entry)}
</p>
<div className="flex shrink-0 items-center gap-1">
<Button
@@ -234,7 +235,7 @@ export function MemorySettingsPage() {
</SelectItem>
{experts.map((a) => (
<SelectItem key={a.id} value={a.id}>
{a.name}
{desensitize(a.name)}
</SelectItem>
))}
</SelectContent>
@@ -272,7 +273,7 @@ export function MemorySettingsPage() {
</SelectItem>
{experts.map((a) => (
<SelectItem key={a.id} value={a.id}>
{a.name}
{desensitize(a.name)}
</SelectItem>
))}
</SelectContent>
@@ -356,7 +357,7 @@ export function MemorySettingsPage() {
<div className="text-muted-foreground mb-1 font-medium">
{m.entryToDeleteLabel}
</div>
<p className="break-words">{entryToDelete.content}</p>
<p className="break-words">{desensitize(entryToDelete.content)}</p>
</div>
) : null}
<DialogFooter>
@@ -539,10 +540,10 @@ function MemoryV1View({
{Math.round(fact.confidence * 100)}%
</span>
</div>
<p className="text-sm whitespace-pre-wrap [overflow-wrap:anywhere]">{fact.content}</p>
<p className="text-sm whitespace-pre-wrap [overflow-wrap:anywhere]">{desensitize(fact.content)}</p>
{fact.sourceError && (
<p className="text-xs text-red-600 bg-red-50 rounded px-2 py-1">
避免:{fact.sourceError}
避免:{desensitize(fact.sourceError)}
</p>
)}
</div>
@@ -566,7 +567,7 @@ function MemoryV1View({
<dt className="font-medium text-muted-foreground mb-0.5">
{CTX_LABELS[key] ?? key}
</dt>
<dd className="whitespace-pre-wrap [overflow-wrap:anywhere]">{value}</dd>
<dd className="whitespace-pre-wrap [overflow-wrap:anywhere]">{desensitize(value)}</dd>
</div>
))}
</dl>
@@ -585,7 +586,7 @@ function MemoryV1View({
<dt className="font-medium text-muted-foreground mb-0.5">
{HISTORY_LABELS[key] ?? key}
</dt>
<dd className="whitespace-pre-wrap [overflow-wrap:anywhere]">{value}</dd>
<dd className="whitespace-pre-wrap [overflow-wrap:anywhere]">{desensitize(value)}</dd>
</div>
))}
</dl>
@@ -686,7 +687,7 @@ function MemorySearchPanel() {
</SelectItem>
{experts.map((a) => (
<SelectItem key={a.id} value={a.id}>
{a.name}
{desensitize(a.name)}
</SelectItem>
))}
</SelectContent>
@@ -719,7 +720,7 @@ function MemorySearchPanel() {
) : results !== null ? (
mode === "reflect" ? (
<div className="rounded-md border p-3 text-sm whitespace-pre-wrap [overflow-wrap:anywhere]">
{reflectText.trim() || m.searchNoResults}
{reflectText.trim() ? desensitize(reflectText.trim()) : m.searchNoResults}
</div>
) : results.length === 0 ? (
<div className="text-muted-foreground rounded-md border border-dashed p-3 text-sm">
@@ -735,7 +736,7 @@ function MemorySearchPanel() {
key={`${index}-${item.slice(0, 16)}`}
className="rounded-md border p-3 text-sm whitespace-pre-wrap [overflow-wrap:anywhere]"
>
{item}
{desensitize(item)}
</div>
))}
</div>
@@ -68,6 +68,7 @@ import {
} from "@/core/skills/hooks";
import type { Skill } from "@/core/skills/type";
import { env } from "@/env";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
import { SettingsSection } from "./settings-section";
@@ -404,7 +405,7 @@ function SkillRow({
>
<ItemTitle>
<div className="flex flex-wrap items-center gap-2">
<span>{skill.name}</span>
<span>{desensitize(skill.name)}</span>
{isBuiltin && (
<Badge variant="secondary" className="text-xs">
{t.settings.skills.badgeBuiltin}
@@ -474,7 +475,7 @@ function SkillRow({
</div>
</ItemTitle>
<ItemDescription className="line-clamp-4">
{skill.description}
{desensitize(skill.description ?? "")}
</ItemDescription>
{skill.use_count !== undefined && (
<div className="text-muted-foreground mt-1 text-xs">
@@ -560,7 +561,9 @@ function SkillRow({
<DialogHeader>
<DialogTitle>{t.settings.skills.deleteAction}</DialogTitle>
<DialogDescription>
{t.settings.skills.deleteConfirm.replace("{name}", skill.name)}
{desensitize(
t.settings.skills.deleteConfirm.replace("{name}", skill.name),
)}
</DialogDescription>
</DialogHeader>
<DialogFooter>
@@ -809,22 +812,22 @@ function InstallSkillDialog({
<div className="flex flex-col gap-2 rounded-md border p-3 text-sm">
<div className="flex items-center gap-2 text-emerald-600">
<CheckCircle2 className="size-4" />
<span className="font-medium">{validation.skill_name}</span>
<span className="font-medium">{desensitize(validation.skill_name)}</span>
</div>
<Field label={dialog.descriptionLabel}>
<span className="line-clamp-3 whitespace-pre-wrap">
{validation.description || "—"}
{validation.description ? desensitize(validation.description) : "—"}
</span>
</Field>
{validation.license && (
<Field label={dialog.licenseLabel}>{validation.license}</Field>
<Field label={dialog.licenseLabel}>{desensitize(validation.license)}</Field>
)}
{validation.allowed_tools.length > 0 && (
<Field label={dialog.toolsLabel}>
<div className="flex flex-wrap gap-1">
{validation.allowed_tools.map((tool) => (
<Badge key={tool} variant="secondary" className="text-xs">
{tool}
{desensitize(tool)}
</Badge>
))}
</div>
@@ -834,7 +837,7 @@ function InstallSkillDialog({
<Field label={dialog.warningsLabel}>
<ul className="list-disc pl-4 text-xs">
{validation.warnings.map((w, idx) => (
<li key={idx}>{w}</li>
<li key={idx}>{desensitize(w)}</li>
))}
</ul>
</Field>
@@ -843,7 +846,9 @@ function InstallSkillDialog({
<div className="text-amber-600 mt-1 flex items-start gap-2 text-xs">
<AlertCircle className="mt-0.5 size-3.5 shrink-0" />
<span>
{dialog.conflictCustom.replace("{name}", validation.skill_name)}
{desensitize(
dialog.conflictCustom.replace("{name}", validation.skill_name),
)}
</span>
</div>
)}
@@ -851,7 +856,9 @@ function InstallSkillDialog({
<div className={cn("mt-1 flex items-start gap-2 text-xs", adminCanOverwritePublic ? "text-amber-600" : "text-destructive")}>
<AlertCircle className="mt-0.5 size-3.5 shrink-0" />
<span>
{(adminCanOverwritePublic ? dialog.conflictPublicAdminHint : dialog.conflictPublic).replace("{name}", validation.skill_name)}
{desensitize(
(adminCanOverwritePublic ? dialog.conflictPublicAdminHint : dialog.conflictPublic).replace("{name}", validation.skill_name),
)}
</span>
</div>
)}
@@ -30,6 +30,7 @@ import {
import { useI18n } from "@/core/i18n/hooks";
import { useDeleteCustomSkill, useSetSkillState } from "@/core/skills/hooks";
import type { Skill } from "@/core/skills/type";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
type Kind = "stale" | "archived";
@@ -146,7 +147,7 @@ function SkillStateRow({ skill, kind }: { skill: Skill; kind: Kind }) {
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<span className="font-medium">{skill.name}</span>
<span className="font-medium">{desensitize(skill.name)}</span>
{isArchived ? (
<Badge variant="secondary" className="text-xs" title={tt.badgeArchivedTitle}>
{tt.badgeArchived}
@@ -180,7 +181,7 @@ function SkillStateRow({ skill, kind }: { skill: Skill; kind: Kind }) {
)}
</div>
<p className="text-muted-foreground mt-1 line-clamp-2 text-sm">
{skill.description}
{desensitize(skill.description ?? "")}
</p>
<p className="text-muted-foreground mt-1 flex flex-wrap gap-x-3 text-xs">
<span>{timeLabel}</span>
@@ -254,7 +255,7 @@ function SkillStateRow({ skill, kind }: { skill: Skill; kind: Kind }) {
<DialogHeader>
<DialogTitle>{tt.deleteAction}</DialogTitle>
<DialogDescription>
{tt.deleteConfirm.replace("{name}", skill.name)}
{desensitize(tt.deleteConfirm.replace("{name}", skill.name))}
</DialogDescription>
</DialogHeader>
<DialogFooter>
@@ -295,12 +296,12 @@ function SkillViewDialog({
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>{skill.name}</DialogTitle>
<DialogTitle>{desensitize(skill.name)}</DialogTitle>
<DialogDescription>{tv.title}</DialogDescription>
</DialogHeader>
<div className="grid gap-3 text-sm">
<Row label={tv.descriptionLabel}>
<span className="whitespace-pre-wrap">{skill.description}</span>
<span className="whitespace-pre-wrap">{desensitize(skill.description ?? "")}</span>
</Row>
<Row label={tv.stateLabel}>
<span className={cn("font-medium", stateColor(skill.state))}>
@@ -34,6 +34,7 @@ import {
} from "@/core/skills/hooks";
import type { Skill } from "@/core/skills/type";
import { reasoningPlugins } from "@/core/streamdown";
import { desensitize } from "@/lib/desensitize";
const markdownClassName =
"!h-auto !w-full max-w-none min-w-0 text-sm leading-7 " +
@@ -147,7 +148,7 @@ export function SkillDetailDialog({
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<DialogTitle className="truncate text-base">
{skill?.name ?? skillName ?? ""}
{desensitize(skill?.name ?? skillName ?? "")}
</DialogTitle>
{skill?.builtin && (
<Badge variant="secondary" className="text-xs">
@@ -165,7 +166,7 @@ export function SkillDetailDialog({
)}
</div>
<DialogDescription className="mt-1 line-clamp-2 text-sm leading-relaxed">
{skill?.description || "-"}
{skill?.description ? desensitize(skill.description) : "-"}
</DialogDescription>
</div>
</div>
@@ -328,7 +329,7 @@ export function SkillDetailDialog({
</div>
) : showRawSource ? (
<pre className="text-foreground/80 max-w-full overflow-x-auto rounded-lg border bg-muted p-4 font-mono text-xs leading-relaxed whitespace-pre-wrap">
{contentData?.content}
{desensitize(contentData?.content ?? "")}
</pre>
) : (
<MarkdownContent
@@ -1,5 +1,6 @@
import { XIcon } from "lucide-react";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
import type { Tag } from "@/core/tags";
@@ -24,7 +25,7 @@ export function TagChip({
className,
)}
>
{tag.name}
{desensitize(tag.name)}
{onRemove && (
<button
type="button"
@@ -3,6 +3,7 @@ import { useEffect } from "react";
import { useI18n } from "@/core/i18n/hooks";
import type { AgentThreadState } from "@/core/threads";
import { desensitize } from "@/lib/desensitize";
import { useThreadChat } from "./chats";
import { FlipDisplay } from "./flip-display";
@@ -44,7 +45,7 @@ export function ThreadTitle({
}
return (
<FlipDisplay uniqueKey={threadId}>
{thread.values.title ?? "Untitled"}
{desensitize(thread.values.title ?? "Untitled")}
</FlipDisplay>
);
}