feat(frontend): 编辑器内「润色 / 再沟通」——选段回炉 + 追加意见迭代出版本栈,接受回填

Phase 1(写作工作台重构):编辑器选中一段即可「润色选段」,复用同步 /refine 回炉
(只读、不写库,守不变量 #3);不满意可「再沟通」——以上一版产出为输入 + 作者新意见
再改一版,形成版本栈(useRefine,已单测)。接受回填按内容重锚落回草稿,原文已变则
提示重选、绝不盲替换(applyRefinement,已单测)。Editor 上报选区,结果以内联卡片展示。
This commit is contained in:
Yaojia Wang
2026-07-07 18:26:47 +02:00
parent 8389572cbb
commit 2f0b84191a
7 changed files with 516 additions and 2 deletions

View File

@@ -4,17 +4,37 @@ import { useEffect, useRef } from "react";
import { cn, focusRing, proseBody } from "@/lib/ui/variants";
export interface EditorSelection {
start: number;
end: number;
text: string;
}
interface EditorProps {
value: string;
onChange: (value: string) => void;
streaming: boolean;
// 选区变化上报(供「润色选段」等选区级 AI 动作取材。空选区也会上报start===end
onSelectionChange?: (selection: EditorSelection) => void;
}
// 中栏正文编辑器宋体、720px 居中、行高 1.9UX §2.3 / §6.3)。
// 流式中显示朱砂光标提示prefers-reduced-motion 下不闪烁,见 globals.css
export function Editor({ value, onChange, streaming }: EditorProps) {
export function Editor({
value,
onChange,
streaming,
onSelectionChange,
}: EditorProps) {
const ref = useRef<HTMLTextAreaElement>(null);
const reportSelection = (el: HTMLTextAreaElement): void => {
if (!onSelectionChange) return;
const start = el.selectionStart;
const end = el.selectionEnd;
onSelectionChange({ start, end, text: value.slice(start, end) });
};
// 自适应高度textarea 高度=内容高度overflow-hidden 不内部滚动),由外层写作区
// 单一滚动条统管。修复正文出现嵌套滚动条 + 定高 60vh 不铺满展示区的怪象。
// min-h-[60vh] 仍作空稿时的最小可写高度(内容更长时按内容增高、外层滚动)。
@@ -35,6 +55,7 @@ export function Editor({ value, onChange, streaming }: EditorProps) {
id="chapter-editor"
value={value}
onChange={(e) => onChange(e.target.value)}
onSelect={(e) => reportSelection(e.currentTarget)}
readOnly={streaming}
aria-busy={streaming}
placeholder="夜色如墨……点击「写本章」让 AI 起草,或直接在此书写。"