11"use client" ;
22
3- import { Fragment , useEffect , useRef , useState } from "react" ;
3+ import { Fragment , useCallback , useEffect , useRef , useState } from "react" ;
44import { ChatForm } from "./chatForm" ;
55import { Heading , StyledMarkdown } from "./markdown" ;
66import { useChatHistoryContext } from "./chatHistory" ;
@@ -14,9 +14,18 @@ import {
1414 PagePath ,
1515} from "@/lib/docs" ;
1616
17- // MarkdownSectionに追加で、ユーザーが今そのセクションを読んでいるかどうか、などの動的な情報を持たせる
17+ /**
18+ * MarkdownSectionに追加で、動的な情報を持たせる
19+ */
1820export type DynamicMarkdownSection = MarkdownSection & {
21+ /**
22+ * ユーザーが今そのセクションを読んでいるかどうか
23+ */
1924 inView : boolean ;
25+ /**
26+ * チャットの会話を元にAIが書き換えた後の内容
27+ */
28+ replacedContent : string ;
2029} ;
2130
2231interface PageContentProps {
@@ -31,25 +40,52 @@ export function PageContent(props: PageContentProps) {
3140 const { setSidebarMdContent } = useSidebarMdContext ( ) ;
3241 const { splitMdContent, pageEntry, path } = props ;
3342
34- // SSR用のローカルstate
35- const [ dynamicMdContent , setDynamicMdContent ] = useState <
36- DynamicMarkdownSection [ ]
37- > (
38- splitMdContent . map ( ( section ) => ( {
39- ...section ,
40- inView : false ,
41- } ) )
42- ) ;
43+ const { chatHistories } = useChatHistoryContext ( ) ;
4344
44- useEffect ( ( ) => {
45- // props.splitMdContentが変わったときにローカルstateとcontextの両方を更新
45+ const initDynamicMdContent = useCallback ( ( ) => {
4646 const newContent = splitMdContent . map ( ( section ) => ( {
4747 ...section ,
4848 inView : false ,
49+ replacedContent : section . rawContent ,
4950 } ) ) ;
51+ const chatDiffs = chatHistories . map ( ( chat ) => chat . diff ) . flat ( ) ;
52+ chatDiffs . sort ( ( a , b ) => a . createdAt . getTime ( ) - b . createdAt . getTime ( ) ) ;
53+ for ( const diff of chatDiffs ) {
54+ const targetSection = newContent . find ( ( s ) => s . id === diff . sectionId ) ;
55+ if ( targetSection ) {
56+ if ( targetSection . replacedContent . includes ( diff . search ) ) {
57+ targetSection . replacedContent = targetSection . replacedContent . replace (
58+ diff . search ,
59+ diff . replace
60+ ) ;
61+ } else {
62+ // TODO: md5ハッシュを参照し過去バージョンのドキュメントへ適用を試みる
63+ console . error (
64+ `Failed to apply diff: search string "${ diff . search } " not found in section ${ targetSection . id } `
65+ ) ;
66+ }
67+ } else {
68+ console . error (
69+ `Failed to apply diff: section with id "${ diff . sectionId } " not found`
70+ ) ;
71+ }
72+ }
73+
74+ return newContent ;
75+ } , [ splitMdContent , chatHistories ] ) ;
76+
77+ // SSR用のローカルstate
78+ const [ dynamicMdContent , setDynamicMdContent ] = useState <
79+ DynamicMarkdownSection [ ]
80+ > ( ( ) => initDynamicMdContent ( ) ) ;
81+
82+ useEffect ( ( ) => {
83+ // props.splitMdContentが変わったとき, チャットのdiffが変わった時に
84+ // ローカルstateとcontextの両方を更新
85+ const newContent = initDynamicMdContent ( ) ;
5086 setDynamicMdContent ( newContent ) ;
5187 setSidebarMdContent ( path , newContent ) ;
52- } , [ splitMdContent , path , setSidebarMdContent ] ) ;
88+ } , [ initDynamicMdContent , path , setSidebarMdContent ] ) ;
5389
5490 const sectionRefs = useRef < Array < HTMLDivElement | null > > ( [ ] ) ;
5591 // sectionRefsの長さをsplitMdContentに合わせる
@@ -87,8 +123,6 @@ export function PageContent(props: PageContentProps) {
87123
88124 const [ isFormVisible , setIsFormVisible ] = useState ( false ) ;
89125
90- const { chatHistories } = useChatHistoryContext ( ) ;
91-
92126 return (
93127 < div
94128 className = "p-4 mx-auto max-w-full grid"
@@ -110,7 +144,7 @@ export function PageContent(props: PageContentProps) {
110144 } }
111145 >
112146 { /* ドキュメントのコンテンツ */ }
113- < StyledMarkdown content = { section . rawContent } />
147+ < StyledMarkdown content = { section . replacedContent } />
114148 </ div >
115149 < div >
116150 { /* 右側に表示するチャット履歴欄 */ }
0 commit comments