From af6e151b9eefa93725e4268696d3124c179273b4 Mon Sep 17 00:00:00 2001 From: Kevin Ho Date: Mon, 25 May 2026 19:50:43 -0700 Subject: [PATCH 1/3] =?UTF-8?q?feat(magic-rewrite):=20V1=20=E2=80=94=20ins?= =?UTF-8?q?truction=20+=20diff=20rewrite=20for=20editors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slim V1 of Magic Rewrite: AI-assisted text rewriting for lorebook, character, and persona editors. - POST /api/magic-rewrite/generate endpoint using existing default agent connection fallback - MagicRewritePanel: instruction textarea, word-level diff view, HelpTooltip noting agent connection usage - Integrated into ExpandedTextarea and LorebookFormFields modals with Apply/Back flow Addresses #1183 (scoped V1 per maintainer feedback). --- .../lorebooks/LorebookFormFields.tsx | 102 +++++++++--- .../src/components/ui/ExpandedTextarea.tsx | 101 ++++++++++-- .../src/components/ui/MagicRewritePanel.tsx | 148 ++++++++++++++++++ packages/server/src/routes/index.ts | 2 + .../server/src/routes/magic-rewrite.routes.ts | 101 ++++++++++++ 5 files changed, 413 insertions(+), 41 deletions(-) create mode 100644 packages/client/src/components/ui/MagicRewritePanel.tsx create mode 100644 packages/server/src/routes/magic-rewrite.routes.ts diff --git a/packages/client/src/components/lorebooks/LorebookFormFields.tsx b/packages/client/src/components/lorebooks/LorebookFormFields.tsx index 4453bdc78..b1c89bef8 100644 --- a/packages/client/src/components/lorebooks/LorebookFormFields.tsx +++ b/packages/client/src/components/lorebooks/LorebookFormFields.tsx @@ -4,10 +4,11 @@ // and LorebookEntryRow (the per-entry inline drawer). // Extracted from LorebookEditor.tsx so styling stays consistent. // ────────────────────────────────────────────── -import { useEffect, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from "react"; -import { FileText, Maximize2, ToggleLeft, ToggleRight, X } from "lucide-react"; +import { useCallback, useEffect, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from "react"; +import { FileText, Maximize2, Sparkles, ToggleLeft, ToggleRight, X } from "lucide-react"; import { cn } from "../../lib/utils"; import { HelpTooltip } from "../ui/HelpTooltip"; +import { MagicRewritePanel } from "../ui/MagicRewritePanel"; export function FieldGroup({ label, @@ -233,6 +234,8 @@ export function ExpandedContentModal({ placeholder?: string; }) { const [local, setLocal] = useState(value); + const [magicRewriteMode, setMagicRewriteMode] = useState(false); + const [magicRewriteResult, setMagicRewriteResult] = useState(""); const textareaRef = useRef(null); useEffect(() => { @@ -241,7 +244,7 @@ export function ExpandedContentModal({ useEffect(() => { const handler = (e: KeyboardEvent) => { - if (e.key === "Escape") { + if (e.key === "Escape" && !magicRewriteMode) { onChange(local); onCommit?.(); onClose(); @@ -249,7 +252,7 @@ export function ExpandedContentModal({ }; document.addEventListener("keydown", handler); return () => document.removeEventListener("keydown", handler); - }, [onClose, onChange, onCommit, local]); + }, [onClose, onChange, onCommit, local, magicRewriteMode]); const handleClose = () => { onChange(local); @@ -257,36 +260,87 @@ export function ExpandedContentModal({ onClose(); }; + const handleMagicRewriteBack = () => { + setMagicRewriteMode(false); + setMagicRewriteResult(""); + }; + + const handleMagicRewriteApply = () => { + if (!magicRewriteResult) return; + setLocal(magicRewriteResult); + setMagicRewriteMode(false); + setMagicRewriteResult(""); + window.setTimeout(() => textareaRef.current?.focus(), 100); + }; + + const handleMagicRewriteResultChange = useCallback((next: string) => { + setMagicRewriteResult(next); + }, []); + return (
-
+
-

{title}

- +

{magicRewriteMode ? "✨ Magic Rewrite" : title}

+
+ +
-