feat: use ReportEditor after research report completed

This commit is contained in:
Jiang Feng
2025-04-27 00:19:13 +08:00
parent 66794a4b73
commit d77d3484c4
4 changed files with 28 additions and 95 deletions

View File

@@ -37,6 +37,7 @@
"@radix-ui/react-tooltip": "^1.2.0", "@radix-ui/react-tooltip": "^1.2.0",
"@t3-oss/env-nextjs": "^0.11.0", "@t3-oss/env-nextjs": "^0.11.0",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@tiptap/react": "^2.11.7",
"best-effort-json-parser": "^1.1.3", "best-effort-json-parser": "^1.1.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",

85
web/pnpm-lock.yaml generated
View File

@@ -8,9 +8,6 @@ importers:
.: .:
dependencies: dependencies:
'@ai-sdk/react':
specifier: ^1.2.9
version: 1.2.9(react@19.1.0)(zod@3.24.3)
'@ant-design/icons': '@ant-design/icons':
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -71,6 +68,9 @@ importers:
'@tailwindcss/typography': '@tailwindcss/typography':
specifier: ^0.5.16 specifier: ^0.5.16
version: 0.5.16(tailwindcss@4.1.4) version: 0.5.16(tailwindcss@4.1.4)
'@tiptap/react':
specifier: ^2.11.7
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
best-effort-json-parser: best-effort-json-parser:
specifier: ^1.1.3 specifier: ^1.1.3
version: 1.1.3 version: 1.1.3
@@ -222,32 +222,6 @@ importers:
packages: packages:
'@ai-sdk/provider-utils@2.2.7':
resolution: {integrity: sha512-kM0xS3GWg3aMChh9zfeM+80vEZfXzR3JEUBdycZLtbRZ2TRT8xOj3WodGHPb06sUK5yD7pAXC/P7ctsi2fvUGQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.23.8
'@ai-sdk/provider@1.1.3':
resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==}
engines: {node: '>=18'}
'@ai-sdk/react@1.2.9':
resolution: {integrity: sha512-/VYm8xifyngaqFDLXACk/1czDRCefNCdALUyp+kIX6DUIYUWTM93ISoZ+qJ8+3E+FiJAKBQz61o8lIIl+vYtzg==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
zod: ^3.23.8
peerDependenciesMeta:
zod:
optional: true
'@ai-sdk/ui-utils@1.2.8':
resolution: {integrity: sha512-nls/IJCY+ks3Uj6G/agNhXqQeLVqhNfoJbuNgCny+nX2veY5ADB91EcZUqVeQ/ionul2SeUswPY6Q/DxteY29Q==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.23.8
'@alloc/quick-lru@5.2.0': '@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -2755,9 +2729,6 @@ packages:
json-schema-traverse@1.0.0: json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
json-stable-stringify-without-jsonify@1.0.1: json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -3609,9 +3580,6 @@ packages:
resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==} resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==}
engines: {node: '>= 10.13.0'} engines: {node: '>= 10.13.0'}
secure-json-parse@2.7.0:
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
selecto@1.26.3: selecto@1.26.3:
resolution: {integrity: sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==} resolution: {integrity: sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==}
@@ -3801,10 +3769,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
throttleit@2.1.0:
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
engines: {node: '>=18'}
tinyglobby@0.2.12: tinyglobby@0.2.12:
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -4034,11 +3998,6 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
zod-to-json-schema@3.24.5:
resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
peerDependencies:
zod: ^3.24.1
zod@3.24.3: zod@3.24.3:
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
@@ -4080,34 +4039,6 @@ packages:
snapshots: snapshots:
'@ai-sdk/provider-utils@2.2.7(zod@3.24.3)':
dependencies:
'@ai-sdk/provider': 1.1.3
nanoid: 3.3.11
secure-json-parse: 2.7.0
zod: 3.24.3
'@ai-sdk/provider@1.1.3':
dependencies:
json-schema: 0.4.0
'@ai-sdk/react@1.2.9(react@19.1.0)(zod@3.24.3)':
dependencies:
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
'@ai-sdk/ui-utils': 1.2.8(zod@3.24.3)
react: 19.1.0
swr: 2.3.3(react@19.1.0)
throttleit: 2.1.0
optionalDependencies:
zod: 3.24.3
'@ai-sdk/ui-utils@1.2.8(zod@3.24.3)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
zod: 3.24.3
zod-to-json-schema: 3.24.5(zod@3.24.3)
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
'@ant-design/colors@8.0.0': '@ant-design/colors@8.0.0':
@@ -6747,8 +6678,6 @@ snapshots:
json-schema-traverse@1.0.0: {} json-schema-traverse@1.0.0: {}
json-schema@0.4.0: {}
json-stable-stringify-without-jsonify@1.0.1: {} json-stable-stringify-without-jsonify@1.0.1: {}
json5@1.0.2: json5@1.0.2:
@@ -7929,8 +7858,6 @@ snapshots:
ajv-formats: 2.1.1(ajv@8.17.1) ajv-formats: 2.1.1(ajv@8.17.1)
ajv-keywords: 5.1.0(ajv@8.17.1) ajv-keywords: 5.1.0(ajv@8.17.1)
secure-json-parse@2.7.0: {}
selecto@1.26.3: selecto@1.26.3:
dependencies: dependencies:
'@daybrush/utils': 1.13.0 '@daybrush/utils': 1.13.0
@@ -8173,8 +8100,6 @@ snapshots:
commander: 2.20.3 commander: 2.20.3
source-map-support: 0.5.21 source-map-support: 0.5.21
throttleit@2.1.0: {}
tinyglobby@0.2.12: tinyglobby@0.2.12:
dependencies: dependencies:
fdir: 6.4.3(picomatch@4.0.2) fdir: 6.4.3(picomatch@4.0.2)
@@ -8495,10 +8420,6 @@ snapshots:
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
zod-to-json-schema@3.24.5(zod@3.24.3):
dependencies:
zod: 3.24.3
zod@3.24.3: {} zod@3.24.3: {}
zustand@4.5.6(@types/react@19.1.2)(react@19.1.0): zustand@4.5.6(@types/react@19.1.2)(react@19.1.0):

View File

@@ -3,6 +3,7 @@
import { useRef } from "react"; import { useRef } from "react";
import ReportEditor from "~/components/editor";
import { useMessage } from "~/core/store"; import { useMessage } from "~/core/store";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
@@ -19,13 +20,20 @@ export function ResearchReportBlock({
}) { }) {
const message = useMessage(messageId); const message = useMessage(messageId);
const contentRef = useRef<HTMLDivElement>(null); const contentRef = useRef<HTMLDivElement>(null);
const isCompleted = message?.isStreaming === false && message?.content !== "";
return ( return (
<div <div
ref={contentRef} ref={contentRef}
className={cn("relative flex flex-col pb-8", className)} className={cn("relative flex flex-col pb-8", className)}
> >
<Markdown animate>{message?.content}</Markdown> {isCompleted ? (
{message?.isStreaming && <LoadingAnimation className="my-12" />} <ReportEditor content={message?.content} />
) : (
<>
<Markdown animate>{message?.content}</Markdown>
{message?.isStreaming && <LoadingAnimation className="my-12" />}
</>
)}
</div> </div>
); );
} }

View File

@@ -14,6 +14,7 @@ import {
handleImageDrop, handleImageDrop,
handleImagePaste, handleImagePaste,
} from "novel"; } from "novel";
import type { Content } from "@tiptap/react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import { defaultExtensions } from "./extensions"; import { defaultExtensions } from "./extensions";
@@ -27,7 +28,7 @@ import GenerativeMenuSwitch from "./generative/generative-menu-switch";
import { uploadFn } from "./image-upload"; import { uploadFn } from "./image-upload";
import { TextButtons } from "./selectors/text-buttons"; import { TextButtons } from "./selectors/text-buttons";
import { slashCommand, suggestionItems } from "./slash-command"; import { slashCommand, suggestionItems } from "./slash-command";
import { defaultEditorContent } from "./content"; // import { defaultEditorContent } from "./content";
import "~/styles/prosemirror.css"; import "~/styles/prosemirror.css";
@@ -35,10 +36,12 @@ const hljs = require("highlight.js");
const extensions = [...defaultExtensions, slashCommand]; const extensions = [...defaultExtensions, slashCommand];
const ReportEditor = () => { export interface ReportEditorProps {
const [initialContent, setInitialContent] = useState<null | JSONContent>( content: Content;
null, }
);
const ReportEditor = ({ content }: ReportEditorProps) => {
const [initialContent, setInitialContent] = useState<Content>(() => content);
const [saveStatus, setSaveStatus] = useState("Saved"); const [saveStatus, setSaveStatus] = useState("Saved");
const [charsCount, setCharsCount] = useState(); const [charsCount, setCharsCount] = useState();
@@ -76,11 +79,11 @@ const ReportEditor = () => {
500, 500,
); );
useEffect(() => { // useEffect(() => {
const content = window.localStorage.getItem("novel-content"); // const content = window.localStorage.getItem("novel-content");
if (content) setInitialContent(JSON.parse(content)); // if (content) setInitialContent(JSON.parse(content));
else setInitialContent(defaultEditorContent); // else setInitialContent(defaultEditorContent);
}, []); // }, []);
if (!initialContent) return null; if (!initialContent) return null;
@@ -103,7 +106,7 @@ const ReportEditor = () => {
<EditorRoot> <EditorRoot>
<EditorContent <EditorContent
immediatelyRender={false} immediatelyRender={false}
initialContent={initialContent} initialContent={initialContent as JSONContent}
extensions={extensions} extensions={extensions}
className="border-muted bg-background relative h-full w-full overflow-auto sm:mb-[calc(20vh)] sm:border sm:shadow-lg" className="border-muted bg-background relative h-full w-full overflow-auto sm:mb-[calc(20vh)] sm:border sm:shadow-lg"
editorProps={{ editorProps={{