@@ -5,7 +5,9 @@ import { useEffect, useRef, useState } from "react";
55import { useParams } from "react-router-dom" ;
66import { useLetterContent } from "./hooks/useLetterContent" ;
77import { streamText } from "./pages/Chat/utils/streamHelper" ;
8- import { buildLetterUserMessage } from "./pages/Chat/utils/letterHelper" ;
8+ import LetterGenerationDialog from "./pages/Letter/components/LetterGenerationDialog" ;
9+ import { buildLetterUserMessage } from "./pages/Letter/utils/letterHelper" ;
10+ import LetterDisclaimer from "./pages/Letter/components/LetterDisclaimer" ;
911
1012export default function Letter ( ) {
1113 const { addMessage, messages, setMessages } = useMessages ( ) ;
@@ -15,6 +17,9 @@ export default function Letter() {
1517 const { org, loc } = useParams ( ) ;
1618 const [ startStreaming , setStartStreaming ] = useState ( false ) ;
1719 const streamLocationRef = useRef < ILocation | null > ( null ) ;
20+ const [ isLoading , setIsLoading ] = useState ( true ) ;
21+ const dialogRef = useRef < HTMLDialogElement > ( null ) ;
22+ const LOADING_DISPLAY_DELAY_MS = 1000 ;
1823
1924 useEffect ( ( ) => {
2025 if ( org === undefined ) return ;
@@ -49,55 +54,78 @@ export default function Letter() {
4954 runGenerateLetter ( ) ;
5055 } , [ messages , startStreaming , addMessage , setMessages ] ) ;
5156
57+ useEffect ( ( ) => {
58+ // Wait for the second message (index 1) which contains the initial AI response
59+ if ( messages . length > 1 && messages [ 1 ] ?. content !== "" ) {
60+ // Include 1s delay for smoother transition
61+ const timeoutId = setTimeout (
62+ ( ) => setIsLoading ( false ) ,
63+ LOADING_DISPLAY_DELAY_MS ,
64+ ) ;
65+ return ( ) => clearTimeout ( timeoutId ) ;
66+ }
67+ } , [ messages ] ) ;
68+
69+ useEffect ( ( ) => {
70+ dialogRef . current ?. showModal ( ) ;
71+ } , [ ] ) ;
72+
5273 return (
53- < div className = "h-dvh pt-16 flex items-center" >
54- < div className = "flex w-full items-center " >
55- < div className = "flex-1 transition-all duration-300" >
56- < div
57- className = { `container relative flex flex-col sm:flex-row gap-4 mx-auto p-6 bg-[#F4F4F2] rounded-lg shadow-[0_4px_6px_rgba(0,0,0,0.1)]
74+ < >
75+ < div className = "h-dvh pt-16 flex items-center" >
76+ < LetterGenerationDialog ref = { dialogRef } />
77+ < div className = "flex w-full items-center" >
78+ < div className = "flex-1 transition-all duration-300 relative" >
79+ < div
80+ className = { `container relative flex flex-col sm:flex-row gap-4 mx-auto p-6 bg-[#F4F4F2] rounded-lg shadow-[0_4px_6px_rgba(0,0,0,0.1)]
5881 ${
5982 isOngoing
6083 ? "justify-between h-[calc(100dvh-4rem-64px)] max-h-[calc(100dvh-4rem-64px)] sm:h-[calc(100dvh-10rem-64px)]"
6184 : "justify-center max-w-[600px]"
6285 } `}
63- >
64- { letterContent !== "" ? (
65- < div className = "flex flex-col gap-4 items-center flex-2/3 h-[40%] sm:h-full" >
66- < div className = "overflow-y-scroll pr-4 w-full" >
67- < span
68- className = "whitespace-pre-wrap generated-letter"
69- dangerouslySetInnerHTML = { {
70- __html : letterContent ,
71- } }
72- />
86+ >
87+ { letterContent !== "" ? (
88+ < div className = "flex flex-col gap-4 items-center flex-2/3 h-[40%] sm:h-full" >
89+ < div className = "overflow-y-scroll pr-4 w-full" >
90+ < span
91+ className = "whitespace-pre-wrap generated-letter"
92+ dangerouslySetInnerHTML = { {
93+ __html : letterContent ,
94+ } }
95+ />
96+ </ div >
7397 </ div >
98+ ) : null }
99+ < div
100+ className = { `flex flex-col ${ letterContent === "" ? "flex-1" : "flex-1/3" } h-[60%] sm:h-full` }
101+ >
102+ { isLoading ? (
103+ < div className = "flex flex-1 items-center justify-center animate-pulse text-lg" >
104+ Generating letter...
105+ </ div >
106+ ) : (
107+ < MessageWindow
108+ messages = { messages }
109+ addMessage = { addMessage }
110+ location = { location }
111+ setLocation = { setLocation }
112+ setMessages = { setMessages }
113+ isOngoing = { isOngoing }
114+ />
115+ ) }
74116 </ div >
75- ) : null }
117+ </ div >
76118 < div
77- className = { `flex flex-col ${ letterContent === "" ? "flex-1 " : "flex-1/3" } h-[60%] sm:h-full ` }
119+ className = { `container mx-auto text-xs px-4 text-center ${ isOngoing ? "max-w-auto my-2 " : "max-w-[600px] my-4" } ` }
78120 >
79- < MessageWindow
80- messages = { messages }
81- addMessage = { addMessage }
82- location = { location }
83- setLocation = { setLocation }
84- setMessages = { setMessages }
85- isOngoing = { isOngoing }
86- />
121+ < p className = { `${ isOngoing ? "mb-0" : "mb-2" } ` } >
122+ < LetterDisclaimer isOngoing = { isOngoing } />
123+ </ p >
124+ < p > For questions, contact michael@qiu-qiulaw.com</ p >
87125 </ div >
88126 </ div >
89- < div
90- className = { `container mx-auto text-xs px-4 text-center ${ isOngoing ? "max-w-auto my-2" : "max-w-[600px] my-4" } ` }
91- >
92- < p className = { `${ isOngoing ? "mb-0" : "mb-2" } ` } >
93- { isOngoing
94- ? "This chatbot offers general housing law info and is not legal advice. For help with your situation, contact a lawyer."
95- : "The information provided by this chatbot is general information only and does not constitute legal advice. While Tenant First Aid strives to keep the content accurate and up to date, completeness and accuracy is not guaranteed. If you have a specific legal issue or question, consider contacting a qualified attorney or a local legal aid clinic for personalized assistance." }
96- </ p >
97- < p > For questions, contact michael@qiu-qiulaw.com</ p >
98- </ div >
99127 </ div >
100128 </ div >
101- </ div >
129+ </ >
102130 ) ;
103131}
0 commit comments