@@ -9,9 +9,10 @@ import { useState, FormEvent, useEffect } from "react";
99// import { getLanguageName } from "../pagesList";
1010import { DynamicMarkdownSection } from "./pageContent" ;
1111import { useEmbedContext } from "@/terminal/embedContext" ;
12- import { askAI } from "@/actions/chatActions" ;
1312import { PagePath } from "@/lib/docs" ;
1413import { useRouter } from "next/navigation" ;
14+ import { ChatStreamEvent } from "@/api/chat/route" ;
15+ import { useStreamingChatContext } from "@/(docs)/streamingChatContext" ;
1516
1617interface ChatFormProps {
1718 path : PagePath ;
@@ -30,6 +31,7 @@ export function ChatForm({ path, sectionContent, close }: ChatFormProps) {
3031 const { files, replOutputs, execResults } = useEmbedContext ( ) ;
3132
3233 const router = useRouter ( ) ;
34+ const streamingChatContext = useStreamingChatContext ( ) ;
3335
3436 // const documentContentInView = sectionContent
3537 // .filter((s) => s.inView)
@@ -64,39 +66,96 @@ export function ChatForm({ path, sectionContent, close }: ChatFormProps) {
6466 const handleSubmit = async ( e : FormEvent < HTMLFormElement > ) => {
6567 e . preventDefault ( ) ;
6668 setIsLoading ( true ) ;
67- setErrorMessage ( null ) ; // Clear previous error message
69+ setErrorMessage ( null ) ;
6870
6971 const userQuestion = inputValue ;
70- // if (!userQuestion && exampleData) {
71- // // 質問が空欄なら、質問例を使用
72- // userQuestion =
73- // exampleData[Math.floor(exampleChoice * exampleData.length)];
74- // setInputValue(userQuestion);
75- // }
76-
77- const result = await askAI ( {
78- path,
79- userQuestion,
80- sectionContent,
81- replOutputs,
82- files,
83- execResults,
84- } ) ;
85-
86- if ( result . error !== null ) {
87- setErrorMessage ( result . error ) ;
88- console . log ( result . error ) ;
89- } else {
90- document . getElementById ( result . chat . sectionId ) ?. scrollIntoView ( {
91- behavior : "smooth" ,
72+
73+ let response : Response ;
74+ try {
75+ response = await fetch ( "/api/chat" , {
76+ method : "POST" ,
77+ headers : { "Content-Type" : "application/json" } ,
78+ body : JSON . stringify ( {
79+ path,
80+ userQuestion,
81+ sectionContent,
82+ replOutputs,
83+ files,
84+ execResults,
85+ } ) ,
9286 } ) ;
93- router . push ( `/chat/${ result . chat . chatId } ` , { scroll : false } ) ;
94- router . refresh ( ) ;
95- setInputValue ( "" ) ;
96- close ( ) ;
87+ } catch {
88+ setErrorMessage ( "AIへの接続に失敗しました" ) ;
89+ setIsLoading ( false ) ;
90+ return ;
91+ }
92+
93+ if ( ! response . ok ) {
94+ setErrorMessage ( `エラーが発生しました (${ response . status } )` ) ;
95+ setIsLoading ( false ) ;
96+ return ;
9797 }
9898
99- setIsLoading ( false ) ;
99+ const reader = response . body ! . getReader ( ) ;
100+ const decoder = new TextDecoder ( ) ;
101+ let buffer = "" ;
102+ let navigated = false ;
103+
104+ // ストリームを非同期で読み続ける(ナビゲーション後もバックグラウンドで継続)
105+ void ( async ( ) => {
106+ try {
107+ while ( true ) {
108+ const result = await reader . read ( ) ;
109+ const { done, value } = result ;
110+ if ( done ) break ;
111+
112+ buffer += decoder . decode ( value , { stream : true } ) ;
113+ const lines = buffer . split ( "\n" ) ;
114+ buffer = lines . pop ( ) ?? "" ;
115+
116+ for ( const line of lines ) {
117+ if ( ! line . trim ( ) ) continue ;
118+ try {
119+ const event = JSON . parse ( line ) as ChatStreamEvent ;
120+
121+ if ( event . type === "chat" ) {
122+ streamingChatContext . startStreaming ( event . chatId ) ;
123+ document . getElementById ( event . sectionId ) ?. scrollIntoView ( {
124+ behavior : "smooth" ,
125+ } ) ;
126+ router . push ( `/chat/${ event . chatId } ` , { scroll : false } ) ;
127+ router . refresh ( ) ;
128+ navigated = true ;
129+ setIsLoading ( false ) ;
130+ setInputValue ( "" ) ;
131+ close ( ) ;
132+ } else if ( event . type === "chunk" ) {
133+ streamingChatContext . appendChunk ( event . text ) ;
134+ } else if ( event . type === "done" ) {
135+ streamingChatContext . finishStreaming ( ) ;
136+ router . refresh ( ) ;
137+ } else if ( event . type === "error" ) {
138+ if ( ! navigated ) {
139+ setErrorMessage ( event . message ) ;
140+ setIsLoading ( false ) ;
141+ }
142+ streamingChatContext . finishStreaming ( ) ;
143+ }
144+ } catch {
145+ // ignore JSON parse errors
146+ }
147+ }
148+ }
149+ } catch ( err ) {
150+ console . error ( "Stream reading failed:" , err ) ;
151+ // ナビゲーション後のエラーはストリーミングを終了してローディングを止める
152+ if ( ! navigated ) {
153+ setErrorMessage ( String ( err ) ) ;
154+ setIsLoading ( false ) ;
155+ }
156+ streamingChatContext . finishStreaming ( ) ;
157+ }
158+ } ) ( ) ;
100159 } ;
101160
102161 return (
0 commit comments