From 909bc421c07d48b857f8287b83e9c6f0dcc02112 Mon Sep 17 00:00:00 2001 From: Mohamed-Abuali Date: Wed, 10 Dec 2025 19:35:55 -0600 Subject: [PATCH] Bugfix/docs gpt widget behavior (#2172) * style(DocsGPTWidget): improve message bubbles and markdown styling - Adjust max-width for message bubbles to 90% for answers and 80% for questions - Add overflow-wrap to prevent text overflow in messages - Update list styling with proper spacing and positioning - Add responsive font sizing for headings using clamp() - Implement custom table styling with proper borders and spacing - Add custom markdown renderer rules for tables * feat(widget): replace input with textarea for prompt input Add support for multi-line input and custom scrollbar styling. Implement Enter key submission handling while allowing Shift+Enter for new lines. * feat(widget): improve textarea auto-resizing and table styling - Add auto-resizing functionality for prompt textarea with min/max height constraints - Fix table cell markup (th/td) and improve scrollbar styling - Add promptRef to manage textarea state and reset after submission * fix(widget): correct table cell styling and prevent empty submissions - Fix swapped td/th elements in markdown renderer - Adjust font weights for table headers and cells - Add validation to prevent empty message submissions * (fix) name mkdwn rule as the returned element --------- Co-authored-by: ManishMadan2882 --- .../src/components/DocsGPTWidget.tsx | 192 ++++++++++++++++-- 1 file changed, 177 insertions(+), 15 deletions(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 5ce9140a..7c16311e 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -293,6 +293,8 @@ const MessageBubble = styled.div<{ type: MESSAGE_TYPE }>` margin: 0px; &:hover ${Feedback} * { visibility: visible ; + + } `; const Message = styled.div<{ type: MESSAGE_TYPE }>` @@ -302,13 +304,14 @@ const Message = styled.div<{ type: MESSAGE_TYPE }>` color: ${props => props.type === 'ANSWER' ? props.theme.primary.text : '#fff'}; border: none; float: ${props => props.type === 'QUESTION' ? 'right' : 'left'}; - max-width: ${props => props.type === 'ANSWER' ? '100%' : '80'}; + max-width: ${props => props.type === 'ANSWER' ? '90%' : '80%'}; overflow: auto; margin: 4px; display: block; line-height: 1.5; padding: 12px; border-radius: 6px; + overflow-wrap: break-word; `; const Markdown = styled.div` pre { @@ -322,7 +325,7 @@ const Markdown = styled.div` } h1 { - font-size: 16px; + font-size: clamp(14px,40vw,16px); } h2 { @@ -334,6 +337,7 @@ const Markdown = styled.div` } p { + margin: 0px; } @@ -354,8 +358,67 @@ const Markdown = styled.div` ul{ padding:0px; - list-style-position: inside; + margin: 1rem 0; + list-style-position: outside; + list-style-type: disc; + padding-left: 1rem; + white-space: normal; } + + ol{ + padding:0px; + margin: 1rem 0; + list-style-position: outside; + list-style-type: decimal; + padding-left: 1rem; + white-space: normal; + } + + li{ + line-height: 1.625; + } + .dgpt-table-container { + margin: 20px 0; + width:100%; + overflow-x: scroll !important; + border: 1px solid #a2a2ab; + border-radius: 6px; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: scrollbar; + scrollbar-width: thin; + scrollbar-color: #a2a2ab #38383b; + } + + + table, .dgpt-table { + width: 100%; + border-collapse: collapse; + text-align: left; + min-width:600px; + + } + thead, .dgpt-thead { + font-size: 12px; + text-transform: uppercase; + + } + + + th, .dgpt-th, td, .dgpt-td { + padding: 10px; + border-bottom: 1px solid #a2a2ab; + font-size:14px; + + } + th{ + font-weight: normal !important; + } + td{ + font-weight: bold; + } + + + ` const ErrorAlert = styled.div` color: #b91c1c; @@ -389,19 +452,49 @@ const Delay = styled(DotAnimation) <{ delay: number }>` `; const PromptContainer = styled.form` background-color: transparent; - height: ${props => props.theme.dimensions.size == 'large' ? '60px' : '40px'}; + min-height: ${props => props.theme.dimensions.size == 'large' ? '40px' : '23px'}; + max-height:150px; display: flex; + align-items: end; justify-content: space-evenly; `; -const StyledInput = styled.input` +const StyledTextarea = styled.textarea` + box-sizing: border-box; width: 100%; border: 1px solid #686877; - padding-left: 12px; + padding: ${props => props.theme.dimensions.size === 'large' ? '18px 12px 14px 12px' : '8px 12px 4px 12px'}; background-color: transparent; font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; border-radius: 6px; color: ${props => props.theme.text}; outline: none; + resize: none; + transition: height 0.1s ease; + overflow-wrap: break-word; + white-space: pre-wrap; + line-height: 1.4; + text-align: left; + min-height: ${props => props.theme.dimensions.size === 'large' ? '60px' : '40px'}; + max-height: 140px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #38383b transparent; + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + &::-webkit-scrollbar-thumb { + background-color: #38383b; + border-radius: 6px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::placeholder { + text-align: left; + + } `; const StyledButton = styled.button` display: flex; @@ -619,7 +712,17 @@ export const WidgetCore = ({ const isBubbleHovered = useRef(false); const conversationRef = useRef(null); const endMessageRef = React.useRef(null); + const promptRef = React.useRef(null); const md = new MarkdownIt(); + //Custom markdown for the table + md.renderer.rules.table_open = () => '
'; + md.renderer.rules.table_close = () => '
'; + md.renderer.rules.thead_open = () => ''; + md.renderer.rules.tr_open = () => ''; + md.renderer.rules.td_open = () => ''; + md.renderer.rules.th_open = () => ''; + + React.useEffect(() => { if (isOpen) { @@ -774,11 +877,7 @@ export const WidgetCore = ({ } } - // submit handler - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - await appendQuery(prompt) - } + const appendQuery = async (userQuery: string) => { if (!userQuery) @@ -788,6 +887,58 @@ export const WidgetCore = ({ queries.push({ prompt: userQuery }); setPrompt(''); await stream(userQuery); + } + // submit handler + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!prompt.trim()) return; + if (promptRef.current) { + promptRef.current.style.height = "auto"; + } + await appendQuery(prompt); + } + const handlePromptKeyDown = async (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + + e.preventDefault(); + // Prevent sending empty messages + if (promptRef.current && promptRef.current.value.trim() === "") return; + //Rest the input to it's original size after submitting + if(promptRef.current){ + promptRef.current.value = ""; + promptRef.current.style.height = "auto"; + } + await appendQuery(prompt); + } +} + // Auto-resize the input textarea while typing, clamping to base or max height + const handleUserInput = (e: React.KeyboardEvent) =>{ + const el = promptRef.current; + if (!el) return; + const baseHeight = size === 'large' ? 60 : 40; + const maxHeight = 140; + el.style.height = 'auto'; + const next = Math.min(el.scrollHeight, maxHeight); + el.style.height = Math.max(baseHeight, next) + 'px'; + + } + + // Update prompt state, auto resize textarea to content, and maintain scroll on new lines + const handlePromptChange = (event: React.ChangeEvent) => { + const value = event.target.value; + setPrompt(value); + const el = event.currentTarget; + const baseHeight = size === 'large' ? 60 : 40; + const maxHeight = 140; + el.style.height = 'auto'; + const next = Math.min(el.scrollHeight, maxHeight); + el.style.height = Math.max(baseHeight, next) + 'px'; + if(value.includes("\n")){ + el.scrollTop = el.scrollHeight; + + } + + } const handleImageError = (event: React.SyntheticEvent) => { event.currentTarget.src = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"; @@ -798,6 +949,9 @@ export const WidgetCore = ({ ? sizesConfig.getCustom(size.custom) : sizesConfig[size]; if (!mounted) return null; + + + return ( {isOpen && size === 'large' && @@ -911,10 +1065,18 @@ export const WidgetCore = ({
- setPrompt(event.target.value)} - type='text' placeholder="Ask your question" /> + onInput={handleUserInput} + value={prompt} + onChange={handlePromptChange} + placeholder="Ask your question" + onKeyDown={handlePromptKeyDown} + rows={1} + wrap="soft" + /> @@ -931,4 +1093,4 @@ export const WidgetCore = ({ } ) -} +} \ No newline at end of file