This commit is contained in:
fengxiangdebaozi committed 2026-05-25 19:30:26 +08:00
1 parent 2f7f07467e
commit ca1d7e77ba
18 files changed
+53 -76

No files matched your search

@@ -1,7 +1,5 @@
import { FileText, PenLine } from 'lucide-react'; import { FileText, PenLine } from 'lucide-react';
import { desensitize } from '@/lib/desensitize';
import { useCanvasGraphContext } from '../../contexts/CanvasGraphContext'; import { useCanvasGraphContext } from '../../contexts/CanvasGraphContext';
import { getCurrentArtifactContent, isCodeArtifact } from '../../utils/artifact'; import { getCurrentArtifactContent, isCodeArtifact } from '../../utils/artifact';
import { TextArtifactRenderer } from '../artifact/TextArtifactRenderer'; import { TextArtifactRenderer } from '../artifact/TextArtifactRenderer';
@@ -47,7 +45,7 @@ export function AIWritingDraftPanel() {
<div className="h-10 border-b flex items-center gap-2 px-4 shrink-0 bg-muted/20"> <div className="h-10 border-b flex items-center gap-2 px-4 shrink-0 bg-muted/20">
<FileText className="w-3.5 h-3.5 text-muted-foreground shrink-0" /> <FileText className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
<span className="text-sm font-medium truncate flex-1"> <span className="text-sm font-medium truncate flex-1">
{content?.title ? desensitize(content.title) : '草稿预览'} {content?.title ?? '草稿预览'}
</span> </span>
</div> </div>
@@ -1,7 +1,5 @@
import { CheckCircle2, ChevronRight, FileText, Search, PenLine, ClipboardEdit, Star } from 'lucide-react'; import { CheckCircle2, ChevronRight, FileText, Search, PenLine, ClipboardEdit, Star } from 'lucide-react';
import { desensitize } from '@/lib/desensitize';
import type { AIWritingSessionSummary, CompletedInterventionRecord } from '../../api/ai-writing-sessions'; import type { AIWritingSessionSummary, CompletedInterventionRecord } from '../../api/ai-writing-sessions';
import { AIWritingTimeline } from './AIWritingTimeline'; import { AIWritingTimeline } from './AIWritingTimeline';
@@ -81,10 +79,10 @@ export function AIWritingHistoryView({ session }: AIWritingHistoryViewProps) {
{/* 元信息 */} {/* 元信息 */}
<div className="px-4 pt-3"> <div className="px-4 pt-3">
<div className="rounded-lg border bg-muted/30 px-3 py-2.5 space-y-1"> <div className="rounded-lg border bg-muted/30 px-3 py-2.5 space-y-1">
<p className="text-xs font-semibold truncate">{desensitize(session.draft_title ?? session.title ?? "")}</p> <p className="text-xs font-semibold truncate">{session.draft_title ?? session.title}</p>
{session.user_intent && ( {session.user_intent && (
<p className="text-[11px] text-muted-foreground leading-relaxed line-clamp-2"> <p className="text-[11px] text-muted-foreground leading-relaxed line-clamp-2">
{desensitize(session.user_intent)} {session.user_intent}
</p> </p>
)} )}
{createdAt && ( {createdAt && (
@@ -4,7 +4,6 @@ import { ChevronsDown, Play, Plus, RotateCcw, Settings2, Square, Trash2 } from '
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { desensitize } from '@/lib/desensitize';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import {
Dialog, Dialog,
@@ -252,7 +251,7 @@ export function AIWritingPanel({
<div className="flex flex-col h-full overflow-hidden bg-background"> <div className="flex flex-col h-full overflow-hidden bg-background">
<div className="flex items-center justify-between px-4 py-2.5 border-b shrink-0"> <div className="flex items-center justify-between px-4 py-2.5 border-b shrink-0">
<span className="text-sm font-semibold truncate flex-1 min-w-0 mr-2"> <span className="text-sm font-semibold truncate flex-1 min-w-0 mr-2">
{desensitize(historySession.draft_title ?? historySession.title)} {historySession.draft_title ?? historySession.title}
</span> </span>
<div className="flex items-center gap-1.5 shrink-0"> <div className="flex items-center gap-1.5 shrink-0">
<Button <Button
@@ -502,7 +501,7 @@ export function AIWritingPanel({
<span className="inline-block size-1.5 rounded-full bg-blue-500 animate-pulse" /> <span className="inline-block size-1.5 rounded-full bg-blue-500 animate-pulse" />
)} )}
{state.status === 'researching' && state.researchProgress {state.status === 'researching' && state.researchProgress
? desensitize(state.researchProgress) ? state.researchProgress
: STATUS_LABEL[state.status] ?? state.status} : STATUS_LABEL[state.status] ?? state.status}
</> </>
)} )}
@@ -518,16 +517,16 @@ export function AIWritingPanel({
</DialogHeader> </DialogHeader>
<div className="space-y-3 py-1 text-sm"> <div className="space-y-3 py-1 text-sm">
<ReqRow label="写作意图"> <ReqRow label="写作意图">
<p className="text-muted-foreground whitespace-pre-wrap leading-relaxed">{desensitize(state.request.userIntent ?? "")}</p> <p className="text-muted-foreground whitespace-pre-wrap leading-relaxed">{state.request.userIntent}</p>
</ReqRow> </ReqRow>
<ReqRow label="文章类型"> <ReqRow label="文章类型">
<span className="text-muted-foreground"> <span className="text-muted-foreground">
{desensitize(articleTypes.find((t) => t.key === state.request!.articleType)?.label ?? state.request.articleType ?? "")} {articleTypes.find((t) => t.key === state.request!.articleType)?.label ?? state.request.articleType}
</span> </span>
</ReqRow> </ReqRow>
{!hideAdvancedFields && ( {!hideAdvancedFields && (
<ReqRow label="目标读者"> <ReqRow label="目标读者">
<span className="text-muted-foreground">{desensitize(state.request.targetAudience ?? "")}</span> <span className="text-muted-foreground">{state.request.targetAudience}</span>
</ReqRow> </ReqRow>
)} )}
<ReqRow label="目标字数"> <ReqRow label="目标字数">
@@ -547,7 +546,7 @@ export function AIWritingPanel({
)} )}
{state.request.modelName && ( {state.request.modelName && (
<ReqRow label="写作模型"> <ReqRow label="写作模型">
<span className="text-muted-foreground">{desensitize(state.request.modelName)}</span> <span className="text-muted-foreground">{state.request.modelName}</span>
</ReqRow> </ReqRow>
)} )}
</div> </div>
@@ -4,7 +4,6 @@ import {
ExternalLink, ListTree, FileText, ExternalLink, ListTree, FileText,
} from 'lucide-react'; } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import { buildIntranetSourceUrl } from '@/core/config'; import { buildIntranetSourceUrl } from '@/core/config';
import type { AIMaterial, CompletedIntervention } from '../../types/ai-writing'; import type { AIMaterial, CompletedIntervention } from '../../types/ai-writing';
import { OutlineCard } from './OutlineCard'; import { OutlineCard } from './OutlineCard';
@@ -105,7 +104,7 @@ function CompletedMaterialItem({ material }: { material: AIMaterial }) {
)} )}
> >
<div className="flex items-start gap-1.5"> <div className="flex items-start gap-1.5">
<span className="flex-1 font-medium leading-snug line-clamp-2">{desensitize(material.title ?? "")}</span> <span className="flex-1 font-medium leading-snug line-clamp-2">{material.title}</span>
{hasDetail && ( {hasDetail && (
<ChevronRight <ChevronRight
className={cn( className={cn(
@@ -116,8 +115,8 @@ function CompletedMaterialItem({ material }: { material: AIMaterial }) {
)} )}
</div> </div>
<div className="flex items-center gap-2 text-muted-foreground"> <div className="flex items-center gap-2 text-muted-foreground">
<span className="truncate max-w-[100px]">{desensitize(material.source ?? "")}</span> <span className="truncate max-w-[100px]">{material.source}</span>
{material.publishedAt && <><span className="text-muted-foreground/40">·</span><span>{desensitize(material.publishedAt)}</span></>} {material.publishedAt && <><span className="text-muted-foreground/40">·</span><span>{material.publishedAt}</span></>}
<div className="flex flex-1 items-center gap-1 min-w-0"> <div className="flex flex-1 items-center gap-1 min-w-0">
<div className="flex-1 h-1 rounded-full bg-muted overflow-hidden min-w-[40px]"> <div className="flex-1 h-1 rounded-full bg-muted overflow-hidden min-w-[40px]">
<div className={cn('h-full rounded-full', barColor)} style={{ width: `${pct}%` }} /> <div className={cn('h-full rounded-full', barColor)} style={{ width: `${pct}%` }} />
@@ -132,7 +131,7 @@ function CompletedMaterialItem({ material }: { material: AIMaterial }) {
<div className="px-2.5 pb-2 pt-1.5 space-y-1.5 border-t border-border/40"> <div className="px-2.5 pb-2 pt-1.5 space-y-1.5 border-t border-border/40">
{material.content && ( {material.content && (
<p className="text-muted-foreground leading-relaxed line-clamp-6"> <p className="text-muted-foreground leading-relaxed line-clamp-6">
{desensitize(material.content.slice(0, 300))} {material.content.slice(0, 300)}
{material.content.length > 300 && '…'} {material.content.length > 300 && '…'}
</p> </p>
)} )}
@@ -170,7 +169,7 @@ function OutlineCompleted({ record, expanded, onToggle }: { record: CompletedInt
已确认大纲 已确认大纲
{record.outline?.title && ( {record.outline?.title && (
<span className="ml-1 text-muted-foreground font-normal"> <span className="ml-1 text-muted-foreground font-normal">
《{desensitize(record.outline.title)}》 《{record.outline.title}》
</span> </span>
)} )}
<span className="ml-1 text-muted-foreground font-normal">共 {sectionCount} 章节</span> <span className="ml-1 text-muted-foreground font-normal">共 {sectionCount} 章节</span>
@@ -197,7 +196,7 @@ function OutlineCompleted({ record, expanded, onToggle }: { record: CompletedInt
// ── 草稿确认历史卡 ──────────────────────────────────────────────────────────── // ── 草稿确认历史卡 ────────────────────────────────────────────────────────────
function DraftCompleted({ record, expanded, onToggle }: { record: CompletedIntervention; expanded: boolean; onToggle: () => void }) { function DraftCompleted({ record, expanded, onToggle }: { record: CompletedIntervention; expanded: boolean; onToggle: () => void }) {
const title = record.draftTitle ? `《${desensitize(record.draftTitle)}》` : '草稿'; const title = record.draftTitle ? `《${record.draftTitle}》` : '草稿';
const wordCount = record.draftWordCount ?? 0; const wordCount = record.draftWordCount ?? 0;
const hasReview = !!record.reviewResult; const hasReview = !!record.reviewResult;
@@ -1,7 +1,6 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Brain, ChevronRight } from 'lucide-react'; import { Brain, ChevronRight } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import type { StreamingDraftSection } from '../../types/ai-writing'; import type { StreamingDraftSection } from '../../types/ai-writing';
import { CollapseTransition } from './CollapseTransition'; import { CollapseTransition } from './CollapseTransition';
@@ -50,7 +49,7 @@ function SectionThinking({ thinkingText, isDone }: { thinkingText: string; isDon
ref={preRef} ref={preRef}
className="text-[10px] font-mono leading-relaxed px-2.5 py-2 text-amber-700/70 dark:text-amber-300/60 whitespace-pre-wrap max-h-32 overflow-y-auto italic" className="text-[10px] font-mono leading-relaxed px-2.5 py-2 text-amber-700/70 dark:text-amber-300/60 whitespace-pre-wrap max-h-32 overflow-y-auto italic"
> >
{desensitize(thinkingText)} {thinkingText}
{!isDone && ( {!isDone && (
<span className="inline-block w-0.5 h-3 bg-amber-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" /> <span className="inline-block w-0.5 h-3 bg-amber-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" />
)} )}
@@ -72,7 +71,7 @@ function SectionBody({ text, showCursor }: { text: string; showCursor: boolean }
if (heading) { if (heading) {
return ( return (
<p key={i} className="text-xs font-semibold text-foreground mt-2 mb-1"> <p key={i} className="text-xs font-semibold text-foreground mt-2 mb-1">
{desensitize(line.slice(heading[0].length))} {line.slice(heading[0].length)}
</p> </p>
); );
} }
@@ -82,7 +81,7 @@ function SectionBody({ text, showCursor }: { text: string; showCursor: boolean }
key={i} key={i}
className="text-xs leading-relaxed text-foreground/90 whitespace-pre-wrap m-0 mb-1" className="text-xs leading-relaxed text-foreground/90 whitespace-pre-wrap m-0 mb-1"
> >
{desensitize(line)} {line}
</p> </p>
); );
})} })}
@@ -109,7 +108,7 @@ export function DraftStreamingView({ title, sections }: DraftStreamingViewProps)
<div className="px-4 pb-4"> <div className="px-4 pb-4">
<div className="rounded-xl border bg-card shadow-sm p-4 prose prose-sm dark:prose-invert max-w-none"> <div className="rounded-xl border bg-card shadow-sm p-4 prose prose-sm dark:prose-invert max-w-none">
{title && ( {title && (
<h1 className="text-base font-bold text-center mb-4 text-foreground">{desensitize(title)}</h1> <h1 className="text-base font-bold text-center mb-4 text-foreground">{title}</h1>
)} )}
{sections.map((sec) => ( {sections.map((sec) => (
@@ -118,7 +117,7 @@ export function DraftStreamingView({ title, sections }: DraftStreamingViewProps)
'text-sm font-semibold mb-1.5', 'text-sm font-semibold mb-1.5',
sec.isDone ? 'text-foreground' : 'text-purple-500', sec.isDone ? 'text-foreground' : 'text-purple-500',
)}> )}>
{desensitize(sec.sectionTitle ?? "")} {sec.sectionTitle}
</h2> </h2>
{/* 该章节的思考过程(可折叠) */} {/* 该章节的思考过程(可折叠) */}
@@ -1,7 +1,5 @@
import { Sparkles } from 'lucide-react'; import { Sparkles } from 'lucide-react';
import { desensitize } from '@/lib/desensitize';
import type { ParsedIntent } from '../../types/ai-writing'; import type { ParsedIntent } from '../../types/ai-writing';
import type { ArticleType } from '../../api/ai-writing-article-types'; import type { ArticleType } from '../../api/ai-writing-article-types';
@@ -34,13 +32,13 @@ export function IntentResultCard({
</span> </span>
</div> </div>
<div className="px-3 py-2.5 space-y-1.5 text-xs"> <div className="px-3 py-2.5 space-y-1.5 text-xs">
<Row label="文章类型" value={desensitize(typeLabel ?? "")} /> <Row label="文章类型" value={typeLabel} />
{!hideTargetAudience && ( {!hideTargetAudience && (
<Row label="目标读者" value={desensitize(intent.targetAudience ?? "")} /> <Row label="目标读者" value={intent.targetAudience} />
)} )}
<Row label="字数目标" value={`${intent.wordCountTarget} 字`} /> <Row label="字数目标" value={`${intent.wordCountTarget} 字`} />
{intent.refinedIntent && ( {intent.refinedIntent && (
<Row label="精炼意图" value={desensitize(intent.refinedIntent)} multiline /> <Row label="精炼意图" value={intent.refinedIntent} multiline />
)} )}
</div> </div>
</div> </div>
@@ -1,8 +1,6 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Sparkles, ChevronDown, ChevronRight, Brain } from 'lucide-react'; import { Sparkles, ChevronDown, ChevronRight, Brain } from 'lucide-react';
import { desensitize } from '@/lib/desensitize';
interface IntentStreamingViewProps { interface IntentStreamingViewProps {
text: string; text: string;
thinkingText?: string; thinkingText?: string;
@@ -56,7 +54,7 @@ export function IntentStreamingView({ text, thinkingText }: IntentStreamingViewP
ref={thinkRef} ref={thinkRef}
className="text-[11px] font-mono leading-relaxed p-3 text-amber-700/70 dark:text-amber-300/60 whitespace-pre-wrap max-h-40 overflow-y-auto italic" className="text-[11px] font-mono leading-relaxed p-3 text-amber-700/70 dark:text-amber-300/60 whitespace-pre-wrap max-h-40 overflow-y-auto italic"
> >
{desensitize(thinkingText)} {thinkingText}
{!text && ( {!text && (
<span className="inline-block w-0.5 h-3.5 bg-amber-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" /> <span className="inline-block w-0.5 h-3.5 bg-amber-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" />
)} )}
@@ -86,7 +84,7 @@ export function IntentStreamingView({ text, thinkingText }: IntentStreamingViewP
ref={preRef} ref={preRef}
className="text-[11px] font-mono leading-relaxed p-3 text-muted-foreground whitespace-pre-wrap max-h-52 overflow-y-auto" className="text-[11px] font-mono leading-relaxed p-3 text-muted-foreground whitespace-pre-wrap max-h-52 overflow-y-auto"
> >
{desensitize(text)} {text}
<span className="inline-block w-0.5 h-3.5 bg-blue-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" /> <span className="inline-block w-0.5 h-3.5 bg-blue-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" />
</pre> </pre>
</div> </div>
@@ -6,7 +6,6 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import type { import type {
AIMaterial, AIOutline, ReviewResult, ReviewIssue, AIMaterial, AIOutline, ReviewResult, ReviewIssue,
ProgressEvent, UserInterventionPayload, ProgressEvent, UserInterventionPayload,
@@ -381,7 +380,7 @@ function DraftConfirmCard({ draftTitle, revisionCount, reviewResult, onSubmit, d
<div> <div>
<p className="text-xs font-medium">草稿已完成,请确认后继续</p> <p className="text-xs font-medium">草稿已完成,请确认后继续</p>
<p className="text-xs text-muted-foreground mt-0.5"> <p className="text-xs text-muted-foreground mt-0.5">
{draftTitle ? `《${desensitize(draftTitle)}》` : '草稿'} {draftTitle ? `《${draftTitle}》` : '草稿'}
{revisionCount > 0 ? `(第 ${revisionCount} 次修改稿)` : '(初稿)'} {revisionCount > 0 ? `(第 ${revisionCount} 次修改稿)` : '(初稿)'}
· 右侧可预览完整内容 · 右侧可预览完整内容
</p> </p>
@@ -617,8 +616,8 @@ function SectionHelpCard({ blockedSections, onSubmit, disabled }: SectionHelpCar
{blockedSections.map((b) => ( {blockedSections.map((b) => (
<div key={b.sectionTitle} className="rounded-md border bg-background p-2.5 space-y-2"> <div key={b.sectionTitle} className="rounded-md border bg-background p-2.5 space-y-2">
<div> <div>
<p className="text-xs font-medium">{desensitize(b.sectionTitle ?? "")}</p> <p className="text-xs font-medium">{b.sectionTitle}</p>
<p className="text-[11px] text-muted-foreground mt-0.5">{desensitize(b.reason ?? "")}</p> <p className="text-[11px] text-muted-foreground mt-0.5">{b.reason}</p>
</div> </div>
<div className="flex gap-1"> <div className="flex gap-1">
{DECISION_OPTIONS.map((opt) => { {DECISION_OPTIONS.map((opt) => {
@@ -1,5 +1,4 @@
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import type { ReviewIssue } from '../../types/ai-writing'; import type { ReviewIssue } from '../../types/ai-writing';
interface IssueListProps { interface IssueListProps {
@@ -94,14 +93,14 @@ function IssueGroup({ label, labelClass, items, selectable, selectedIndices, onT
<span className={cn('rounded px-1.5 py-0.5 text-[10px] font-medium', CATEGORY_COLOR[issue.category] ?? 'bg-muted text-muted-foreground')}> <span className={cn('rounded px-1.5 py-0.5 text-[10px] font-medium', CATEGORY_COLOR[issue.category] ?? 'bg-muted text-muted-foreground')}>
{CATEGORY_LABEL[issue.category] ?? issue.category} {CATEGORY_LABEL[issue.category] ?? issue.category}
</span> </span>
<span className="text-muted-foreground truncate">{desensitize(issue.location ?? "")}</span> <span className="text-muted-foreground truncate">{issue.location}</span>
</div> </div>
<p className={cn('text-foreground leading-snug', selectable ? 'pl-5' : '')}> <p className={cn('text-foreground leading-snug', selectable ? 'pl-5' : '')}>
{desensitize(issue.issue ?? "")} {issue.issue}
</p> </p>
<p className={cn('text-muted-foreground leading-snug', selectable ? 'pl-5' : '')}> <p className={cn('text-muted-foreground leading-snug', selectable ? 'pl-5' : '')}>
<span className="text-primary mr-1">→</span> <span className="text-primary mr-1">→</span>
{desensitize(issue.suggestion ?? "")} {issue.suggestion}
</p> </p>
</div> </div>
); );
@@ -1,7 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { ChevronRight, ExternalLink } from 'lucide-react'; import { ChevronRight, ExternalLink } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import { buildIntranetSourceUrl } from '@/core/config'; import { buildIntranetSourceUrl } from '@/core/config';
import type { AIMaterial } from '../../types/ai-writing'; import type { AIMaterial } from '../../types/ai-writing';
import { CollapseTransition } from './CollapseTransition'; import { CollapseTransition } from './CollapseTransition';
@@ -52,7 +51,7 @@ export function MaterialItem({ material, checked, onToggle }: MaterialItemProps)
className="flex flex-1 items-start gap-1.5 text-left" className="flex flex-1 items-start gap-1.5 text-left"
> >
<span className={cn('flex-1 font-medium leading-snug line-clamp-2', !checked && 'text-muted-foreground')}> <span className={cn('flex-1 font-medium leading-snug line-clamp-2', !checked && 'text-muted-foreground')}>
{desensitize(material.title ?? "")} {material.title}
</span> </span>
<ChevronRight <ChevronRight
className={cn( className={cn(
@@ -65,11 +64,11 @@ export function MaterialItem({ material, checked, onToggle }: MaterialItemProps)
{/* 元信息行 */} {/* 元信息行 */}
<div className="flex items-center gap-2 px-2.5 pb-2 pl-8"> <div className="flex items-center gap-2 px-2.5 pb-2 pl-8">
<span className="text-muted-foreground truncate max-w-[120px]">{desensitize(material.source ?? "")}</span> <span className="text-muted-foreground truncate max-w-[120px]">{material.source}</span>
{material.publishedAt && ( {material.publishedAt && (
<> <>
<span className="text-muted-foreground/40">·</span> <span className="text-muted-foreground/40">·</span>
<span className="text-muted-foreground shrink-0">{desensitize(material.publishedAt)}</span> <span className="text-muted-foreground shrink-0">{material.publishedAt}</span>
</> </>
)} )}
{/* 相关度色条 */} {/* 相关度色条 */}
@@ -91,7 +90,7 @@ export function MaterialItem({ material, checked, onToggle }: MaterialItemProps)
<CollapseTransition open={expanded}> <CollapseTransition open={expanded}>
<div className="px-2.5 pb-2.5 pl-8 space-y-1.5 border-t"> <div className="px-2.5 pb-2.5 pl-8 space-y-1.5 border-t">
<p className="text-muted-foreground leading-relaxed pt-2 line-clamp-6"> <p className="text-muted-foreground leading-relaxed pt-2 line-clamp-6">
{desensitize(material.content.slice(0, 300))} {material.content.slice(0, 300)}
{material.content.length > 300 && '…'} {material.content.length > 300 && '…'}
</p> </p>
{material.recUuid && ( {material.recUuid && (
@@ -1,7 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { ListTree, Plus } from 'lucide-react'; import { ListTree, Plus } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { desensitize } from '@/lib/desensitize';
import type { AIOutline, OutlineSection as OutlineSectionType } from '../../types/ai-writing'; import type { AIOutline, OutlineSection as OutlineSectionType } from '../../types/ai-writing';
import { OutlineSection } from './OutlineSection'; import { OutlineSection } from './OutlineSection';
import { CollapseTransition } from './CollapseTransition'; import { CollapseTransition } from './CollapseTransition';
@@ -93,7 +92,7 @@ export function OutlineCard({ outline, onChange, readOnly = false }: OutlineCard
className={editable ? 'flex-1 font-semibold cursor-text hover:text-primary' : 'flex-1 font-semibold'} className={editable ? 'flex-1 font-semibold cursor-text hover:text-primary' : 'flex-1 font-semibold'}
onClick={() => editable && setEditingTitle(true)} onClick={() => editable && setEditingTitle(true)}
> >
{outline.title ? desensitize(outline.title) : <span className="text-muted-foreground italic font-normal">(未设置标题)</span>} {outline.title || <span className="text-muted-foreground italic font-normal">(未设置标题)</span>}
</span> </span>
)} )}
</div> </div>
@@ -1,6 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { ChevronRight, Plus, X, GripVertical } from 'lucide-react'; import { ChevronRight, Plus, X, GripVertical } from 'lucide-react';
import { desensitize } from '@/lib/desensitize';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { OutlineSection as OutlineSectionType } from '../../types/ai-writing'; import type { OutlineSection as OutlineSectionType } from '../../types/ai-writing';
import { CollapseTransition } from './CollapseTransition'; import { CollapseTransition } from './CollapseTransition';
@@ -101,7 +100,7 @@ export function OutlineSection({
className={cn('flex-1 font-medium', !readOnly && 'cursor-text hover:text-primary')} className={cn('flex-1 font-medium', !readOnly && 'cursor-text hover:text-primary')}
onClick={() => !readOnly && setEditingTitle(true)} onClick={() => !readOnly && setEditingTitle(true)}
> >
{section.sectionTitle ? desensitize(section.sectionTitle) : <span className="text-muted-foreground italic">(未命名)</span>} {section.sectionTitle || <span className="text-muted-foreground italic">(未命名)</span>}
</span> </span>
)} )}
@@ -120,7 +119,7 @@ export function OutlineSection({
<div key={i} className="flex items-center gap-1.5 pt-1.5"> <div key={i} className="flex items-center gap-1.5 pt-1.5">
<span className="text-muted-foreground/40 shrink-0">•</span> <span className="text-muted-foreground/40 shrink-0">•</span>
{readOnly ? ( {readOnly ? (
<span className="flex-1 text-muted-foreground">{desensitize(kp)}</span> <span className="flex-1 text-muted-foreground">{kp}</span>
) : ( ) : (
<input <input
value={kp} value={kp}
@@ -1,8 +1,6 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { FileText, ChevronDown, ChevronRight, Brain } from 'lucide-react'; import { FileText, ChevronDown, ChevronRight, Brain } from 'lucide-react';
import { desensitize } from '@/lib/desensitize';
interface OutlineStreamingViewProps { interface OutlineStreamingViewProps {
text: string; text: string;
thinkingText?: string; thinkingText?: string;
@@ -52,7 +50,7 @@ export function OutlineStreamingView({ text, thinkingText }: OutlineStreamingVie
ref={thinkRef} ref={thinkRef}
className="text-[11px] font-mono leading-relaxed p-3 text-amber-700/70 dark:text-amber-300/60 whitespace-pre-wrap max-h-40 overflow-y-auto italic" className="text-[11px] font-mono leading-relaxed p-3 text-amber-700/70 dark:text-amber-300/60 whitespace-pre-wrap max-h-40 overflow-y-auto italic"
> >
{desensitize(thinkingText)} {thinkingText}
{/* 思考仍在进行时显示光标 */} {/* 思考仍在进行时显示光标 */}
{!text && ( {!text && (
<span className="inline-block w-0.5 h-3.5 bg-amber-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" /> <span className="inline-block w-0.5 h-3.5 bg-amber-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" />
@@ -84,7 +82,7 @@ export function OutlineStreamingView({ text, thinkingText }: OutlineStreamingVie
ref={preRef} ref={preRef}
className="text-[11px] font-mono leading-relaxed p-3 text-muted-foreground whitespace-pre-wrap max-h-52 overflow-y-auto" className="text-[11px] font-mono leading-relaxed p-3 text-muted-foreground whitespace-pre-wrap max-h-52 overflow-y-auto"
> >
{desensitize(text)} {text}
<span className="inline-block w-0.5 h-3.5 bg-purple-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" /> <span className="inline-block w-0.5 h-3.5 bg-purple-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" />
</pre> </pre>
</div> </div>
@@ -1,7 +1,6 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { CheckCircle2, Loader2 } from 'lucide-react'; import { CheckCircle2, Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import type { StreamingReviewItem, ReviewIssue } from '../../types/ai-writing'; import type { StreamingReviewItem, ReviewIssue } from '../../types/ai-writing';
const CATEGORY_META: Record< const CATEGORY_META: Record<
@@ -42,9 +41,9 @@ function IssueRow({ issue }: { issue: ReviewIssue }) {
)}> )}>
{issue.severity === 'major' ? '严重' : '轻微'} {issue.severity === 'major' ? '严重' : '轻微'}
</span> </span>
<span className="text-muted-foreground">{desensitize(issue.issue ?? "")}</span> <span className="text-muted-foreground">{issue.issue}</span>
{issue.suggestion && ( {issue.suggestion && (
<span className="text-orange-600 dark:text-orange-400"> → {desensitize(issue.suggestion)}</span> <span className="text-orange-600 dark:text-orange-400"> → {issue.suggestion}</span>
)} )}
</div> </div>
); );
@@ -74,10 +73,10 @@ function StreamingText({ item }: { item: StreamingReviewItem }) {
> >
{item.thinkingText && ( {item.thinkingText && (
<span className="italic text-amber-600/70 dark:text-amber-400/60"> <span className="italic text-amber-600/70 dark:text-amber-400/60">
{desensitize(item.thinkingText)} {item.thinkingText}
</span> </span>
)} )}
{desensitize(item.streamingText ?? "")} {item.streamingText}
<span className="inline-block w-0.5 h-3 bg-orange-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" /> <span className="inline-block w-0.5 h-3 bg-orange-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" />
</pre> </pre>
); );
@@ -1,6 +1,5 @@
import { CheckCircle2, XCircle } from 'lucide-react'; import { CheckCircle2, XCircle } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import type { RevisionSummary } from '../../types/ai-writing'; import type { RevisionSummary } from '../../types/ai-writing';
interface RevisionSummaryCardProps { interface RevisionSummaryCardProps {
@@ -50,7 +49,7 @@ export function RevisionSummaryCard({ summary }: RevisionSummaryCardProps) {
)}> )}>
{CATEGORY_LABEL[iss.category] ?? iss.category} {CATEGORY_LABEL[iss.category] ?? iss.category}
</span> </span>
<span>{desensitize(iss.issue ?? "")}</span> <span>{iss.issue}</span>
</div> </div>
))} ))}
</div> </div>
@@ -65,7 +64,7 @@ export function RevisionSummaryCard({ summary }: RevisionSummaryCardProps) {
</p> </p>
{skippedIssues.map((iss, i) => ( {skippedIssues.map((iss, i) => (
<div key={i} className="flex items-start gap-1.5 pl-5 text-muted-foreground/60 leading-snug line-through"> <div key={i} className="flex items-start gap-1.5 pl-5 text-muted-foreground/60 leading-snug line-through">
<span>{desensitize(iss.issue ?? "")}</span> <span>{iss.issue}</span>
</div> </div>
))} ))}
</div> </div>
@@ -1,6 +1,5 @@
import { AlertCircle, AlertTriangle, CheckCircle2, Circle, Loader2, PauseCircle } from 'lucide-react'; import { AlertCircle, AlertTriangle, CheckCircle2, Circle, Loader2, PauseCircle } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import type { ProgressEvent } from '../../types/ai-writing'; import type { ProgressEvent } from '../../types/ai-writing';
type StepStatus = 'done' | 'running' | 'paused' | 'waiting' | 'error' | 'warning'; type StepStatus = 'done' | 'running' | 'paused' | 'waiting' | 'error' | 'warning';
@@ -50,7 +49,7 @@ export function TimelineStep({ event, isLast, children }: TimelineStepProps) {
AGENT_COLOR[event.agentName] ?? 'text-muted-foreground', AGENT_COLOR[event.agentName] ?? 'text-muted-foreground',
)} )}
> >
{desensitize(event.agentName)} {event.agentName}
</span> </span>
)} )}
@@ -71,11 +70,11 @@ export function TimelineStep({ event, isLast, children }: TimelineStepProps) {
: 'text-amber-600 dark:text-amber-500', : 'text-amber-600 dark:text-amber-500',
)} )}
> >
{desensitize(event.title || event.message || (isError ? '出错了' : '提示'))} {event.title || event.message || (isError ? '出错了' : '提示')}
</p> </p>
{event.detail && ( {event.detail && (
<p className="mt-0.5 text-[11px] leading-relaxed text-muted-foreground break-words"> <p className="mt-0.5 text-[11px] leading-relaxed text-muted-foreground break-words">
{desensitize(event.detail)} {event.detail}
</p> </p>
)} )}
{/* 接口解析失败时的原始错误响应体,默认折叠,点击展开 */} {/* 接口解析失败时的原始错误响应体,默认折叠,点击展开 */}
@@ -85,13 +84,13 @@ export function TimelineStep({ event, isLast, children }: TimelineStepProps) {
错误返回结果(点击展开) 错误返回结果(点击展开)
</summary> </summary>
<pre className="mt-1 max-h-60 overflow-auto whitespace-pre-wrap break-all rounded bg-muted/60 p-2 text-[10px] leading-relaxed text-muted-foreground"> <pre className="mt-1 max-h-60 overflow-auto whitespace-pre-wrap break-all rounded bg-muted/60 p-2 text-[10px] leading-relaxed text-muted-foreground">
{desensitize(event.raw)} {event.raw}
</pre> </pre>
</details> </details>
)} )}
</div> </div>
) : ( ) : (
<span className="ml-1.5 text-xs text-muted-foreground">{desensitize(event.message ?? "")}</span> <span className="ml-1.5 text-xs text-muted-foreground">{event.message}</span>
)} )}
{children} {children}
@@ -1,5 +1,4 @@
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { desensitize } from '@/lib/desensitize';
import type { WritingStatus } from '../../types/ai-writing'; import type { WritingStatus } from '../../types/ai-writing';
interface WritingSkeletonCardProps { interface WritingSkeletonCardProps {
@@ -38,7 +37,7 @@ function MaterialsSkeleton({ researchProgress }: { researchProgress?: string })
{/* 标题行:展示素材收集专家的实时进度 */} {/* 标题行:展示素材收集专家的实时进度 */}
<div className="flex items-center gap-2 text-xs text-blue-500 font-medium"> <div className="flex items-center gap-2 text-xs text-blue-500 font-medium">
<ThinkingDots color="bg-blue-400" /> <ThinkingDots color="bg-blue-400" />
<span>{researchProgress ? desensitize(researchProgress) : '素材收集专家搜索中…'}</span> <span>{researchProgress || '素材收集专家搜索中…'}</span>
</div> </div>
{/* 关键词标签占位 */} {/* 关键词标签占位 */}
@@ -4,7 +4,6 @@ import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { desensitize } from "@/lib/desensitize";
import type { CanvasArtifactText } from "../../types/artifact"; import type { CanvasArtifactText } from "../../types/artifact";
import { TiptapEditor } from "../../editor/TiptapEditor"; import { TiptapEditor } from "../../editor/TiptapEditor";
import { useWritingEditor } from "../../hooks/useWritingEditor"; import { useWritingEditor } from "../../hooks/useWritingEditor";
@@ -248,7 +247,7 @@ export function TextArtifactRenderer({ content, isStreaming }: TextArtifactRende
}, },
}} }}
> >
{desensitize(content.fullMarkdown)} {content.fullMarkdown}
</ReactMarkdown> </ReactMarkdown>
<span className="inline-block w-0.5 h-4 bg-primary animate-pulse align-middle ml-0.5" /> <span className="inline-block w-0.5 h-4 bg-primary animate-pulse align-middle ml-0.5" />
</div> </div>