import { Bot, CheckCheck, RefreshCcw, WandSparkles } from 'lucide-react' import { startTransition, useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { configureMonaco, editorTheme, sharedOptions, } from '@/components/markdown-workbench' import { LazyDiffEditor } from '@/components/lazy-monaco' import { MarkdownPreview } from '@/components/markdown-preview' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { adminApi, ApiError } from '@/lib/api' import { computeDiffHunks, applySelectedDiffHunks } from '@/lib/markdown-merge' import { loadDraftWindowSnapshot, savePolishWindowResult, type DraftWindowSnapshot, } from '@/lib/post-draft-window' type PolishTarget = 'editor' | 'create' function getDraftKey() { if (typeof window === 'undefined') { return null } return new URLSearchParams(window.location.search).get('draftKey') } function getTarget(): PolishTarget { if (typeof window === 'undefined') { return 'editor' } const value = new URLSearchParams(window.location.search).get('target') return value === 'create' ? 'create' : 'editor' } function buildApplyMessage(draftKey: string, markdown: string, target: PolishTarget) { return { type: 'termi-admin-post-polish-apply', draftKey, markdown, target, } } export function PostPolishPage() { const draftKey = getDraftKey() const target = getTarget() const [snapshot, setSnapshot] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [polishing, setPolishing] = useState(false) const [polishedMarkdown, setPolishedMarkdown] = useState('') const [selectedIds, setSelectedIds] = useState>(new Set()) useEffect(() => { const draft = loadDraftWindowSnapshot(draftKey) if (!draft) { setError('没有找到要润色的草稿快照,请从文章编辑页重新打开 AI 润色窗口。') } else { startTransition(() => { setSnapshot(draft) }) } setLoading(false) }, [draftKey]) const originalMarkdown = snapshot?.markdown ?? '' const hunks = useMemo( () => (polishedMarkdown ? computeDiffHunks(originalMarkdown, polishedMarkdown) : []), [originalMarkdown, polishedMarkdown], ) const mergedMarkdown = useMemo( () => applySelectedDiffHunks(originalMarkdown, hunks, selectedIds), [hunks, originalMarkdown, selectedIds], ) const applyAll = () => { setSelectedIds(new Set(hunks.map((hunk) => hunk.id))) } const keepOriginal = () => { setSelectedIds(new Set()) } const applyToParent = () => { if (!draftKey) { toast.error('当前窗口缺少草稿标识,无法回填。') return } const result = savePolishWindowResult(draftKey, mergedMarkdown, target) window.opener?.postMessage(buildApplyMessage(draftKey, mergedMarkdown, target), window.location.origin) toast.success('已把 AI 润色结果回填到原编辑器。') return result } return (
AI 润色工作台

{snapshot?.title || 'AI 润色与选择性合并'}

左边保留润色前的原稿,右边是当前选中的合并结果。你可以先生成 AI 润色稿,再按改动块决定要保留哪些内容。

{loading ? ( 正在加载草稿快照... ) : error ? ( AI 润色窗口加载失败 {error} ) : snapshot ? (
润色前 vs 当前合并结果 按改动块选择是否采用 AI 润色结果。
改动块 {hunks.length} 已采用 {selectedIds.size} 目标 {target === 'create' ? '新建草稿' : '现有文章'}
润色前原稿 当前合并结果
当前合并结果预览 边挑选改动,边查看最终会保存成什么样。
改动块选择 每一块都可以单独采用或保留原文,合并结果会立即同步到右侧 diff 和预览。 {!polishedMarkdown ? (
先点“生成 AI 润色稿”,这里才会出现可选的改动块。
) : hunks.length ? ( hunks.map((hunk, index) => { const accepted = selectedIds.has(hunk.id) return (

改动块 {index + 1}

原文 {hunk.originalStart}-{Math.max(hunk.originalEnd, hunk.originalStart - 1)} 行 ,润色稿 {hunk.modifiedStart}-{Math.max(hunk.modifiedEnd, hunk.modifiedStart - 1)} 行

{hunk.preview}

) }) ) : (
AI 已返回结果,但没有检测到行级差异。可以直接应用,或者重新生成一次。
)}
) : null}
) }