- Notifications
You must be signed in to change notification settings - Fork 1.6k
Ksm#331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base:main
Are you sure you want to change the base?
Ksm #331
Uh oh!
There was an error while loading. Please reload this page.
Conversation
tbwksm commented Oct 24, 2025 • edited by coderabbitai bot
Loading Uh oh!
There was an error while loading. Please reload this page.
edited by coderabbitai bot
Uh oh!
There was an error while loading. Please reload this page.
Ksm/initial upload
출처 박스 변경. 디자인 반영.
refresh button, pdf icon
…ubble components. Adjust styling and improve menu alias display formatting.
- Added dynamic dropdown width calculation based on option labels. - Introduced `dropdownMinWidth` signal and `triggerRef` for measurement. - Updated input field and dropdown styling for better alignment and responsiveness.
…` handling in BotBubble - Added `observeMastClick` observer to `observersConfigType`. - Enhanced UI and interactivity for `mastSearches` in BotBubble component.
…le components - Introduced `calledTools` state with corresponding signal and update logic. - Enhanced LoadingBubble to display active tool calls dynamically. - Added logic to reset `calledTools` state during relevant Bot events.
- Introduced `clipboardSrc` property in BotBubble and related types. - Updated copy-to-clipboard logic to use custom `clipboardSrc` image if provided.
This reverts commit a7a542e.
…ents - Introduced `thoughts` property in Bot state and incorporated parsing logic for `<think>` tags. - Enhanced menu sorting logic to prioritize mentions in messages for better context relevance. - Updated LoadingBubble with avatar display options and improved interaction handling.
…ast searches in BotBubble - Introduced ordered state management for menus, sources, and mast searches. - Enhanced link handling logic to prioritize relevance and improve user interaction. - Updated styles and added dynamic rendering for ordered items.
- Introduced `docNumberMap` for consistent document numbering across sessions. - Adjusted link handling to assign and display unique numbers based on document IDs. - Fixed duplicated source ordering issue by refining `orderedCurrentSources` updates.
- Removed unused button rendering for menus and mast IDs. - Simplified the component's structure by eliminating redundant interactivity.
- Eliminated redundant span element and inline styles for unused icon rendering in `SourceBubble`. - Simplified component structure by cleaning up unnecessary code.
…e BotBubble styles - Eliminated unnecessary `sourceDocuments` references in `apiMessage` objects for cleaner state updates. - Simplified and consolidated inline styling logic in BotBubble component. - Ensured proper handling of null/undefined `sourceDocuments` for consistent message formatting.
… message merge logic - Implemented caching for `choose_one_property` selections within the session to streamline responses. - Enhanced action handling for `choose_one_property` and search-related operations, ensuring appropriate AI message formatting and updates. - Improved consistency in merging and updating consecutive AI messages. - Updated BotBubble component styles for better rendering of vertical buttons in `choose_one_property` actions.
…erty` logic - Introduced `isAppending` prop to refine LoadingBubble rendering, improving message transitions. - Simplified and optimized `choose_one_property` action handling, including caching and fallback logic. - Enhanced Bot component interactivity and removed redundant code for better maintainability.
- Cleaned up debug logs from `choose_one_property` and `search` actions for better code maintainability.
- Ensured focus is maintained for Backspace key events. - Added event listeners to stop propagation for Backspace and deleteContentBackward actions during capture phase. - Refactored ref assignment to handle user-provided refs and cleanup listeners effectively.
- Added support for external element triggers to toggle bot visibility via `externalTriggerElementId`. - Introduced event dispatching for open, close, and toggle actions (`flowise:open`, `flowise:close`, `flowise:toggle`). - Enhanced `Bubble.tsx` to conditionally render button and tooltip based on the `hideButton` property. - Updated window exports to include toggle control methods.
- Introduced `CloseButton` component with customizable `closeButtonColor` and observer close functionality (`useObserverClose`). - Added `showCloseButton` prop for conditional rendering of the close button in Bot and Bubble components. - Updated event listeners in Bubble to improve external trigger handling.
coderabbitaibot commented Oct 30, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
WalkthroughThis pull request enhances a chatbot widget system with expanded streaming message handling, observer callbacks for user interactions, Korean language localization, language-aware text truncation utilities, a new ComboBox input component, additional UI refinements (avatars, bubbles, buttons), and new window-level control functions (open/close/toggle) for external bot triggering. The Bot component gains support for model/module selectors, close buttons, and metadata preservation across multiple message types. Changes
Sequence Diagram(s)sequenceDiagram participant User as User participant BubbleUI as Bubble UI participant Bot as Bot Component participant Observer as Observer Callbacks participant Storage as LocalStorage User->>BubbleUI: Triggers external element or flowise:open event BubbleUI->>Bot: toggleBot() / dispatch event Bot->>Bot: Handle streaming message<br/>(append vs. new) Bot->>Bot: Merge menus/mastSearches<br/>into last AI message Bot->>Observer: observeMessages(updated messages) Bot->>Storage: Persist messages with<br/>new metadata Bot->>BotBubble: Render with menus/sources<br/>and specialized links User->>BotBubble: Click menu: link BotBubble->>Observer: observeMenuClick(menu) Observer->>User: Callback triggered User->>BubbleUI: Select gptModel from ComboBox BubbleUI->>Bot: onChange(modelValue) Bot->>Bot: Save to chatflowConfig.gptModel Bot->>Storage: Update config Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/buttons/SendButton.tsx (1)
3-3: Remove unused import.The
DeleteIconcomponent is imported but no longer used in this file, as it has been replaced with an inline SVG in the DeleteButton component.Apply this diff to remove the unused import:
-import{DeleteIcon, SendIcon } from '../icons';+import{SendIcon } from '../icons';src/components/bubbles/StarterPromptBubble.tsx (1)
4-26: Use the newbackgroundColorprop.Line 24 keeps the background hard-coded to white while
Propsnow requiresbackgroundColor. Callers must supply a value that is never applied, so the new API is broken. Please bind the style to the prop.- 'background-color': 'white',+ 'background-color': props.backgroundColor,
♻️ Duplicate comments (1)
src/components/buttons/FeedbackButtons.tsx (1)
25-25: Consider using an i18n/l10n system for button titles.Like in
FeedbackContentDialog.tsx, the hardcoded Korean tooltip titles make the UI Korean-only. For a maintainable multilingual approach, use an i18n library with language selection.Example refactor:
const{ t }=useI18n();// CopyToClipboardButtontitle={t('buttons.copyToClipboard')}// ThumbsUpButton title={t('buttons.thumbsUp')}// ThumbsDownButtontitle={t('buttons.thumbsDown')}Also applies to: 45-45, 65-65
🧹 Nitpick comments (13)
src/components/FeedbackContentDialog.tsx (1)
48-48: Consider using an i18n/l10n system instead of hardcoded Korean strings.Hardcoding Korean text makes the UI Korean-only and difficult to maintain for multilingual support. Consider using an i18n library (e.g.,
solid-i18n,i18next) with language detection/selection.Additionally, there's a spelling error on line 83: "메세지" should be "메시지" (correct Korean spelling for "message").
Example with an i18n system:
import{useI18n}from'./i18n';// hypotheticalconstFeedbackContentDialog=(props: FeedbackContentDialogProps)=>{const{ t }=useI18n();return(// ...<span>{t('feedback.title')}</span>// ...<textareaplaceholder={t('feedback.placeholder')}/>// ...<button>{t('feedback.submit')}</button>);};Also applies to: 83-83, 93-93
src/components/buttons/SendButton.tsx (1)
59-83: LGTM! Consider extracting common button logic.The CloseButton implementation is solid and follows the established pattern. The use of
type="button"is correct for a non-submit action, and the default title provides good accessibility.However, there's significant code duplication across SendButton, DeleteButton, and CloseButton (button structure, class composition, loading state handling, style attributes). Consider extracting a base button component or a shared composition function to reduce duplication and improve maintainability.
src/components/icons/ClipboardIcon.tsx (1)
4-30: Remove the commented legacy SVG.The old 24×24 SVG is now commented out, which adds noise without serving a fallback. Let’s delete the unused block so the component stays lean.
- // <svg- // xmlns="http://www.w3.org/2000/svg"- // class="icon icon-tabler icon-tabler-refresh w-4 h-4"- // width="24"- // height="24"- // viewBox="0 0 24 24"- // fill="none"- // stroke={props.color ?? defaultButtonColor}- // stroke-width="2"- // stroke-linecap="round"- // stroke-linejoin="round"- // >- // <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />- // <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />- // </svg>src/components/icons/ChevronDownIcon.tsx (1)
3-11: Drop the unusedisCurrentColorprop.
isCurrentColoris declared but never referenced, which invites confusion. Please remove it (or wire it through) so the public API only exposes meaningful props.-type Props ={- class?: string;- isCurrentColor?: boolean;-};+type Props ={+ class?: string;+};src/features/bubble/types.ts (1)
140-144: Consider optional fields for ComboBoxTheme.All fields in
ComboBoxThemeare required, which might be restrictive. Consider:
- Making
defaultValueoptional with a fallback to the first value in the array- Making
labeloptional for cases where a label isn't neededThis would provide more flexibility while maintaining type safety.
export type ComboBoxTheme ={- label: string;- defaultValue: string;+ label?: string;+ defaultValue?: string; values:{value: string; label: string }[]};src/utils/index.ts (1)
154-155: Use English comments for consistency.The exports are correct, but the Korean comment should be translated to English for codebase consistency.
-// 텍스트 자르기 관련 유틸리티 함수들+// Text truncation utility functions export{truncateTextByWidth, getTextWidth } from './textTruncator';src/utils/textTruncator.test.ts (2)
1-66: Use a proper testing framework with assertions.This test file uses console.log statements instead of a proper testing framework. Consider migrating to Jest, Vitest, or another testing framework with assertions.
Example with Jest/Vitest:
import{describe,it,expect}from'vitest';import{truncateTextByWidth,getTextWidth}from'./textTruncator';describe('Text Truncation Utilities',()=>{describe('truncateTextByWidth',()=>{it('should truncate English text correctly',()=>{constresult=truncateTextByWidth('Hello World!',10);expect(getTextWidth(result)).toBeLessThanOrEqual(10);});it('should handle Korean text',()=>{constresult=truncateTextByWidth('안녕하세요 반갑습니다',10);expect(getTextWidth(result)).toBeLessThanOrEqual(10);});it('should handle empty strings',()=>{expect(truncateTextByWidth('',10)).toBe('');});});describe('getTextWidth',()=>{it('should calculate width correctly for mixed text',()=>{constwidth=getTextWidth('Hello 안녕');expect(width).toBeGreaterThan(0);});});});Additionally, translate Korean comments to English for consistency:
- '=== 텍스트 자르기 함수 테스트 ===' → '=== Text Truncation Function Tests ==='
- '영어 텍스트 (10글자 너비)' → 'English text (10 width units)'
- etc.
68-76: Integrate tests into the build pipeline.The tests are only executed manually in development mode in the browser. Consider:
- Adding a test script to package.json
- Running tests as part of CI/CD
- Removing the conditional execution since test runners handle environment concerns
This would ensure tests run consistently across all environments and catch regressions early.
public/index.html (1)
22-138: Consider externalizing configuration for maintainability.The initialization contains a large, deeply nested configuration object with many hardcoded values (URLs, user-specific data, UI settings). While functional, this makes the file difficult to maintain and update.
Consider:
- Moving the configuration to a separate JSON or JS module
- Using environment-specific configuration files
- Documenting which values are intended to be customized vs. defaults
This would improve reusability and make it easier to maintain different deployment configurations.
src/components/bubbles/SourceBubble.tsx (3)
85-85: Magic number for truncation width needs clarification.The
maxWidthvalue of50appears arbitrary. Consider:
- Extracting this as a named constant with a descriptive name (e.g.,
MAX_TEXT_WIDTH_WITH_IMAGE)- Documenting why this specific value was chosen
- Calculating it dynamically based on the container dimensions if possible
Example:
+const MAX_TEXT_WIDTH_WITH_IMAGE = 50; // Chosen to leave room for 74px image below+ export const SourceBubble = (props: Props) => ( <> <div ... > ... -{props.imageSrc && props.imageSrc.trim() !== '' ? truncateTextByWidth(props.chunkContent, 50) : props.chunkContent}+{props.imageSrc && props.imageSrc.trim() !== '' ? truncateTextByWidth(props.chunkContent, MAX_TEXT_WIDTH_WITH_IMAGE) : props.chunkContent}
23-32: Fixed dimensions may limit responsiveness.The hardcoded width and height (
139px) restrict the component's ability to adapt to different screen sizes or container contexts. While this may be intentional for a uniform grid layout, consider whether responsive sizing would be beneficial.If responsiveness is desired, consider using percentage-based dimensions or CSS Grid/Flexbox for flexible layouts.
88-96: Consider error handling for invalid base64 images.If
props.imageSrccontains invalid base64 data, the image will fail to render without user feedback. Consider adding error handling to gracefully handle this scenario.Example:
<imgstyle={{ ... }}src={`data:image/png;base64,${props.imageSrc}`}alt="source"onError={(e)=>{// Hide broken image or show placeholder(e.targetasHTMLImageElement).style.display='none';}}/>src/features/bubble/components/Bubble.tsx (1)
74-90: Simplify external trigger binding logic.The external trigger implementation has two code paths: direct element binding (lines 76-81) and document-level delegation (lines 82-89). The fallback delegation is only needed when the element isn't immediately available, but both paths always execute, creating unnecessary complexity.
Consider simplifying by using only the delegation approach, which handles both cases:
createEffect(() =>{if (!externalTriggerElementId) return; - const el = document.getElementById(externalTriggerElementId);- if (el){- const handler = () => toggleBot();- el.addEventListener('click', handler);- return () => el.removeEventListener('click', handler);- } const handler = (e: Event) =>{const target = e.target as HTMLElement | null; if (!target) return; const el = target.closest(`#${externalTriggerElementId}`); if (el) toggleBot()}; document.addEventListener('click', handler, true); return () => document.removeEventListener('click', handler, true)});Or, if direct binding is preferred for performance, choose one approach based on whether the element exists at initialization.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (154)
dist/components/Badge.d.tsis excluded by!**/dist/**dist/components/Badge.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/Bot.d.tsis excluded by!**/dist/**dist/components/Bot.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/FeedbackContentDialog.d.tsis excluded by!**/dist/**dist/components/FeedbackContentDialog.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/ImageUploadButton.d.tsis excluded by!**/dist/**dist/components/ImageUploadButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/RecordAudioButton.d.tsis excluded by!**/dist/**dist/components/RecordAudioButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/RichTreeView.d.tsis excluded by!**/dist/**dist/components/RichTreeView.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/SendButton.d.tsis excluded by!**/dist/**dist/components/SendButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/TreeViewDemo.d.tsis excluded by!**/dist/**dist/components/TreeViewDemo.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/TypingBubble.d.tsis excluded by!**/dist/**dist/components/TypingBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/avatars/Avatar.d.tsis excluded by!**/dist/**dist/components/avatars/Avatar.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/avatars/DefaultAvatar.d.tsis excluded by!**/dist/**dist/components/avatars/DefaultAvatar.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/bubbles/AgentReasoningBubble.d.tsis excluded by!**/dist/**dist/components/bubbles/AgentReasoningBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/bubbles/BotBubble.d.tsis excluded by!**/dist/**dist/components/bubbles/BotBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/bubbles/FollowUpPromptBubble.d.tsis excluded by!**/dist/**dist/components/bubbles/FollowUpPromptBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/bubbles/GuestBubble.d.tsis excluded by!**/dist/**dist/components/bubbles/GuestBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/bubbles/LeadCaptureBubble.d.tsis excluded by!**/dist/**dist/components/bubbles/LeadCaptureBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/bubbles/LoadingBubble.d.tsis excluded by!**/dist/**dist/components/bubbles/LoadingBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/bubbles/SourceBubble.d.tsis excluded by!**/dist/**dist/components/bubbles/SourceBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/bubbles/StarterPromptBubble.d.tsis excluded by!**/dist/**dist/components/bubbles/StarterPromptBubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/buttons/AttachmentUploadButton.d.tsis excluded by!**/dist/**dist/components/buttons/AttachmentUploadButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/buttons/CancelButton.d.tsis excluded by!**/dist/**dist/components/buttons/CancelButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/buttons/FeedbackButtons.d.tsis excluded by!**/dist/**dist/components/buttons/FeedbackButtons.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/buttons/ImageUploadButton.d.tsis excluded by!**/dist/**dist/components/buttons/ImageUploadButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/buttons/LeadCaptureButtons.d.tsis excluded by!**/dist/**dist/components/buttons/LeadCaptureButtons.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/buttons/RecordAudioButton.d.tsis excluded by!**/dist/**dist/components/buttons/RecordAudioButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/buttons/SendButton.d.tsis excluded by!**/dist/**dist/components/buttons/SendButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/examples/TreeViewExample.d.tsis excluded by!**/dist/**dist/components/examples/TreeViewExample.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/examples/index.d.tsis excluded by!**/dist/**dist/components/examples/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/AddImageIcon.d.tsis excluded by!**/dist/**dist/components/icons/AddImageIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/AttachmentIcon.d.tsis excluded by!**/dist/**dist/components/icons/AttachmentIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/CircleDotIcon.d.tsis excluded by!**/dist/**dist/components/icons/CircleDotIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/ClipboardIcon.d.tsis excluded by!**/dist/**dist/components/icons/ClipboardIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/DeleteIcon.d.tsis excluded by!**/dist/**dist/components/icons/DeleteIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/RecordIcon.d.tsis excluded by!**/dist/**dist/components/icons/RecordIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/SendIcon.d.tsis excluded by!**/dist/**dist/components/icons/SendIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/SparklesIcon.d.tsis excluded by!**/dist/**dist/components/icons/SparklesIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/ThumbsDownIcon.d.tsis excluded by!**/dist/**dist/components/icons/ThumbsDownIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/ThumbsUpIcon.d.tsis excluded by!**/dist/**dist/components/icons/ThumbsUpIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/TickIcon.d.tsis excluded by!**/dist/**dist/components/icons/TickIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/TrashIcon.d.tsis excluded by!**/dist/**dist/components/icons/TrashIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/XIcon.d.tsis excluded by!**/dist/**dist/components/icons/XIcon.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/icons/index.d.tsis excluded by!**/dist/**dist/components/icons/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/index.d.tsis excluded by!**/dist/**dist/components/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/inputs/textInput/components/FilePreview.d.tsis excluded by!**/dist/**dist/components/inputs/textInput/components/FilePreview.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/inputs/textInput/components/ShortTextInput.d.tsis excluded by!**/dist/**dist/components/inputs/textInput/components/ShortTextInput.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/inputs/textInput/components/TextInput.d.tsis excluded by!**/dist/**dist/components/inputs/textInput/components/TextInput.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/inputs/textInput/index.d.tsis excluded by!**/dist/**dist/components/inputs/textInput/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/treeview/DataTransformer.d.tsis excluded by!**/dist/**dist/components/treeview/DataTransformer.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/treeview/RichTreeView.d.tsis excluded by!**/dist/**dist/components/treeview/RichTreeView.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/treeview/TreeView.d.tsis excluded by!**/dist/**dist/components/treeview/TreeView.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/components/treeview/WorkflowTreeView.d.tsis excluded by!**/dist/**dist/components/treeview/WorkflowTreeView.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/constants.d.tsis excluded by!**/dist/**dist/constants.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/bubble/components/Bubble.d.tsis excluded by!**/dist/**dist/features/bubble/components/Bubble.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/bubble/components/BubbleButton.d.tsis excluded by!**/dist/**dist/features/bubble/components/BubbleButton.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/bubble/components/Tooltip.d.tsis excluded by!**/dist/**dist/features/bubble/components/Tooltip.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/bubble/components/index.d.tsis excluded by!**/dist/**dist/features/bubble/components/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/bubble/index.d.tsis excluded by!**/dist/**dist/features/bubble/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/bubble/types.d.tsis excluded by!**/dist/**dist/features/bubble/types.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/full/components/Full.d.tsis excluded by!**/dist/**dist/features/full/components/Full.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/full/components/index.d.tsis excluded by!**/dist/**dist/features/full/components/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/full/index.d.tsis excluded by!**/dist/**dist/features/full/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/popup/components/DisclaimerPopup.d.tsis excluded by!**/dist/**dist/features/popup/components/DisclaimerPopup.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/popup/components/Popup.d.tsis excluded by!**/dist/**dist/features/popup/components/Popup.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/popup/components/index.d.tsis excluded by!**/dist/**dist/features/popup/components/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/popup/index.d.tsis excluded by!**/dist/**dist/features/popup/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/features/popup/types.d.tsis excluded by!**/dist/**dist/features/popup/types.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/index.d.tsis excluded by!**/dist/**dist/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/queries/sendMessageQuery.d.tsis excluded by!**/dist/**dist/queries/sendMessageQuery.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/register.d.tsis excluded by!**/dist/**dist/register.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/types.d.tsis excluded by!**/dist/**dist/types.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/utils/audioRecording.d.tsis excluded by!**/dist/**dist/utils/audioRecording.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/utils/chatInputHistory.d.tsis excluded by!**/dist/**dist/utils/chatInputHistory.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/utils/index.d.tsis excluded by!**/dist/**dist/utils/index.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/utils/isMobileSignal.d.tsis excluded by!**/dist/**dist/utils/isMobileSignal.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/web.d.tsis excluded by!**/dist/**dist/web.d.ts.mapis excluded by!**/dist/**,!**/*.mapdist/web.jsis excluded by!**/dist/**dist/web.umd.jsis excluded by!**/dist/**dist/window.d.tsis excluded by!**/dist/**dist/window.d.ts.mapis excluded by!**/dist/**,!**/*.map
📒 Files selected for processing (29)
.gitattributes(1 hunks).gitignore(1 hunks)README.md(1 hunks)public/index.html(1 hunks)src/assets/index.css(1 hunks)src/components/Bot.tsx(40 hunks)src/components/FeedbackContentDialog.tsx(4 hunks)src/components/avatars/Avatar.tsx(1 hunks)src/components/bubbles/BotBubble.tsx(12 hunks)src/components/bubbles/GuestBubble.tsx(1 hunks)src/components/bubbles/LoadingBubble.tsx(1 hunks)src/components/bubbles/SourceBubble.tsx(2 hunks)src/components/bubbles/StarterPromptBubble.tsx(2 hunks)src/components/buttons/FeedbackButtons.tsx(3 hunks)src/components/buttons/SendButton.tsx(1 hunks)src/components/icons/ChevronDownIcon.tsx(1 hunks)src/components/icons/ClipboardIcon.tsx(1 hunks)src/components/icons/index.ts(1 hunks)src/components/inputs/ComboBox.tsx(1 hunks)src/components/inputs/index.ts(1 hunks)src/components/inputs/textInput/components/ShortTextInput.tsx(1 hunks)src/features/bubble/components/Bubble.tsx(4 hunks)src/features/bubble/types.ts(4 hunks)src/features/full/components/Full.tsx(1 hunks)src/queries/sendMessageQuery.ts(2 hunks)src/utils/index.ts(1 hunks)src/utils/textTruncator.test.ts(1 hunks)src/utils/textTruncator.ts(1 hunks)src/window.ts(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
src/components/buttons/SendButton.tsx (1)
src/components/buttons/RecordAudioButton.tsx (1)
Spinner(31-47)
src/utils/textTruncator.ts (1)
src/utils/index.ts (2)
truncateTextByWidth(155-155)getTextWidth(155-155)
src/components/bubbles/SourceBubble.tsx (2)
src/utils/textTruncator.ts (1)
truncateTextByWidth(7-35)src/utils/index.ts (1)
truncateTextByWidth(155-155)
src/components/bubbles/LoadingBubble.tsx (3)
src/utils/isMobileSignal.ts (1)
isMobile(3-3)src/components/avatars/Avatar.tsx (1)
Avatar(6-24)src/components/TypingBubble.tsx (1)
TypingBubble(1-7)
src/utils/textTruncator.test.ts (2)
src/utils/textTruncator.ts (2)
getTextWidth(84-90)truncateTextByWidth(7-35)src/utils/index.ts (2)
getTextWidth(155-155)truncateTextByWidth(155-155)
src/components/inputs/ComboBox.tsx (2)
src/components/inputs/index.ts (1)
ComboBox(2-2)src/components/icons/ChevronDownIcon.tsx (1)
ChevronDownIcon(8-12)
src/components/bubbles/BotBubble.tsx (4)
src/components/Bot.tsx (1)
MessageType(116-138)src/queries/sendMessageQuery.ts (1)
updateFeedbackQuery(73-79)src/components/avatars/Avatar.tsx (1)
Avatar(6-24)src/components/bubbles/SourceBubble.tsx (1)
SourceBubble(11-101)
src/components/buttons/FeedbackButtons.tsx (2)
src/components/buttons/SendButton.tsx (1)
Spinner(85-101)src/components/icons/ThumbsDownIcon.tsx (1)
ThumbsDownIcon(3-19)
src/features/bubble/components/Bubble.tsx (1)
src/features/bubble/components/BubbleButton.tsx (1)
BubbleButton(20-146)
src/queries/sendMessageQuery.ts (1)
src/utils/index.ts (1)
sendRequest(9-73)
src/components/Bot.tsx (5)
src/features/bubble/types.ts (1)
ComboBoxTheme(140-144)src/queries/sendMessageQuery.ts (1)
sendMessageLog(134-140)src/components/inputs/ComboBox.tsx (1)
ComboBox(31-251)src/components/inputs/index.ts (1)
ComboBox(2-2)src/components/bubbles/LoadingBubble.tsx (1)
LoadingBubble(28-50)
🪛 Gitleaks (8.28.0)
public/index.html
[high] 33-33: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (20)
.gitignore (1)
2-2: LGTM!Adding
distto.gitignoreis standard practice for build output directories. This aligns well with the PR's expanded UI component system and build artifacts..gitattributes (1)
1-42: Good addition for cross-platform consistency.This
.gitattributesfile properly configures Git to normalize line endings across the project. The setup—withtext=autoas a default, explicit LF enforcement for source files and configs, and binary declarations—is a solid best practice that will help prevent line-ending inconsistencies across contributors on different operating systems.The coverage is appropriate for a TypeScript/JavaScript web project.
src/components/FeedbackContentDialog.tsx (1)
20-20: Validation change may not be effective without HTML5 constraints.The switch from checking
inputValue()toinputRef?.reportValidity()is a step toward HTML5 validation, but the textarea element (lines 74-85) has no validation attributes likerequired,minlength, etc. Without constraints,reportValidity()will always returntrue, making this check effectively a no-op.If validation is needed, add constraints to the textarea:
<textarea onInput={(e) => handleInput(e.currentTarget.value)} ref={inputRef as HTMLTextAreaElement} rows="4" + required class="block p-2.5 rounded-lg border focus:ring-blue-500 focus:border-blue-500 bg-transparent flex-1 w-full feedback-input disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 font-normal"Otherwise, if no validation is required, you can revert to the simpler check or remove it entirely.
src/queries/sendMessageQuery.ts (2)
18-18: LGTM!Adding
aimlHostto theBaseRequesttype is a clean design choice that allows all request functions to optionally target a separate AIML host.
134-140: The code is syntactically valid; the review comment's "syntax error" claim is incorrect.The missing space in
chatflowid,aimlHoston line 134 is a style inconsistency, not a syntax error—both{a,b}and{a, b}are valid JavaScript/TypeScript. The code parses and executes without errors.The endpoint is properly integrated: Bot.tsx (lines 555–560) calls
sendMessageLog(), passingaimlHostfrom config variables (vars.aimlUrl), which overrides the default port 8443 fallback shown in public/index.html line 28.Likely an incorrect or invalid review comment.
src/components/buttons/SendButton.tsx (1)
48-54: LGTM!The inline SVG implementation is clean and consistent with the CloseButton approach. Using
props.sendButtonColorfor dynamic fill color provides good flexibility.src/components/inputs/textInput/components/ShortTextInput.tsx (1)
40-49: Address Korean comments and clarify multi-layered Backspace handling; note that ref cleanup is valid SolidJS pattern.The review comment contains factually incorrect analysis. The ref callback returning a cleanup function is the correct SolidJS pattern—not a memory leak. SolidJS ref callbacks properly support cleanup functions, and the
removeEventListenercalls are appropriate resource cleanup.However, the following concerns remain valid:
Three layers of Backspace handling (lines 41–49, 63–71, 74–80) appear redundant. Please clarify why all three are necessary, or consolidate to a single approach.
Korean comments should be replaced with English for international collaboration (lines 41, 44, 55, 63, 66, 69, 74, 83).
Type inconsistency:
props.refis typed asHTMLInputElement | HTMLTextAreaElement | undefined(line 6), but the code treats it as a callback function (line 57:typeof props.ref === 'function') and assigns to it directly (line 58:(props.ref as any) = node). Either update the type definition or fix the assignment logic.Likely an incorrect or invalid review comment.
src/assets/index.css (1)
202-203: LGTM! Clean visual polish.The removal of the border and change to pure white background (#ffffff) creates a cleaner, more modern look for host bubbles.
src/features/full/components/Full.tsx (1)
96-100: LGTM! Consistent prop forwarding.The new props are properly forwarded from
theme.chatWindowto the Bot component, following the established pattern in this file.src/components/bubbles/GuestBubble.tsx (1)
88-90: Verify mobile layout with increased margin.The left margin increase from 50px to 100px provides better visual separation on desktop, but may cause layout issues on mobile devices with narrow viewports.
Consider testing on mobile/narrow viewports to ensure the guest bubble container doesn't overflow or create horizontal scrolling.
src/components/inputs/index.ts (1)
1-2: LGTM! Clean barrel exports.The exports follow standard patterns and provide a clean public API for input components.
src/features/bubble/types.ts (3)
31-31: LGTM! Speech-to-text feature flag.The addition of
isSpeechToTextEnabledprovides a clean way to toggle speech-to-text functionality via theme configuration.
43-49: LGTM! Enhanced bot message theming.The new properties enable richer bot message presentation with state-specific avatars and emphasized backgrounds, while maintaining backward compatibility through optional fields.
90-94: LGTM! Model/module selectors and close button controls.The addition of
gptModelsandmdmModules(typed as ComboBoxTheme) provides a structured way to configure model and module selection, while the close button properties offer flexible control over window dismissal behavior.README.md (1)
87-108: LGTM! Clear and helpful documentation.The new "Smart Text Truncation" section provides clear explanations of the feature with practical usage examples. The width unit specifications for different character types are well-documented.
src/utils/textTruncator.ts (1)
7-35: Consider edge case: empty string after truncation.If
maxWidthis very small (e.g., less than the width of a single character), the function will return only'...'without any original content. While this may be acceptable, consider whether a minimum width validation or a different behavior (e.g., returning at least one character) would be more appropriate.Verify the expected behavior when
maxWidthis smaller than the first character's width. If this is intentional, consider adding a comment documenting this edge case.src/components/bubbles/LoadingBubble.tsx (2)
16-26: Good implementation of loading animation.The
LoadingDotscomponent correctly manages its interval lifecycle withonMountandonCleanup. The modulo arithmetic creates a smooth 0-3 dots animation pattern.
30-34: Avatar placeholder alignment is well-handled.The fallback div maintains proper spacing when
isAppendingis true by matching the avatar dimensions. This ensures consistent layout during message streaming.src/features/bubble/components/Bubble.tsx (2)
60-72: External event integration is well-implemented.The lifecycle management for
flowise:open,flowise:close, andflowise:toggleevents properly registers listeners in acreateEffectand cleans them up on unmount. This enables external code to control the bot programmatically.
138-138: Clarify positioning calculation—not a circular dependency, but verify button drag behavior.The concern about "circular dependency" is incorrect. The variables
chatWindowBottom,buttonSize, andbuttonBottomare all statically calculated once; they don't create recursive dependencies.However, there is a real consistency issue:
chatWindowBottomis a fixed value computed at mount, butbuttonPosition()is reactive and changes when the button is dragged. The constraintMath.min(..., window.innerHeight - chatWindowBottom)therefore uses an outdated reference frame after dragging.The Tooltip component (line 51 in Tooltip.tsx) uses the correct dynamic formula:
position.bottom + buttonSize + 10. The chat window should do the same, or document why a static constraint is intentional.Verify:
- Does button drag behavior cause the chat window to misalign or overlap?
- Is the static constraint intentional, or should it recalculate on button position changes?
| langCode: "KO", | ||
| mdmUrl: "https://rnd.tobeway.com:8000/tbwapi", | ||
| userId: "ksm", | ||
| userToken: "gejDolc+90dtL1t+mszjAPT0bIMdgqHCplmFf66mJ9s=" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove hardcoded credentials from source code.
The userToken field contains what appears to be a sensitive authentication token. Hardcoding credentials in source files, especially in HTML that may be committed to version control, poses a significant security risk.
Consider these alternatives:
- Use environment variables or a configuration service to inject credentials at runtime
- For demo/test purposes, use placeholder values and document where real credentials should be provided
- If this is meant to be a template, replace with a clear placeholder like
"YOUR_TOKEN_HERE"
Apply this approach for other sensitive values as well (lines 24, 28, 31, 32).
🧰 Tools
🪛 Gitleaks (8.28.0)
[high] 33-33: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🤖 Prompt for AI Agents
public/index.html lines 24, 28, 31, 32 and specifically line 33: remove the hardcoded sensitive token and any other hardcoded credentials and replace them with clear placeholder values (e.g. "YOUR_TOKEN_HERE"); update the page to obtain real credentials at runtime via environment-backed injection or a secure configuration endpoint (server-side templating, build-time env substitution, or fetching a secure config API) and add a short comment documenting where/how to supply real credentials for deployment; ensure no real secrets remain in the committed file. | } | ||
| > | ||
| <imgsrc={avatarSrc()}alt="Bot avatar"class="rounded-full object-cover w-full h-full"/> | ||
| <imgsrc={avatarSrc()}alt="Bot avatar"class="object-cover w-full h-full"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore circular mask on avatar image.
Line 20 drops rounded-full from the <img>, and because the parent <figure> doesn’t hide overflow, the image now renders with square corners outside the circular frame. Please reinstate the masking so avatars remain round.
- <img src={avatarSrc()} alt="Bot avatar" class="object-cover w-full h-full" />+ <img src={avatarSrc()} alt="Bot avatar" class="rounded-full object-cover w-full h-full" />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <imgsrc={avatarSrc()}alt="Bot avatar"class="object-cover w-full h-full"/> | |
| <imgsrc={avatarSrc()}alt="Bot avatar"class="rounded-full object-cover w-full h-full"/> |
🤖 Prompt for AI Agents
In src/components/avatars/Avatar.tsx around line 20, the <img> lost its circular mask and now shows square corners because the parent <figure> doesn't hide overflow; restore the avatar's round shape by adding the rounded-full Tailwind class back onto the image (preserve the existing object-cover w-full h-full classes) and ensure you use the JSX prop name className instead of class if this is a React/TSX file. | uploadsConfig={{ | ||
| ...uploadsConfig(), | ||
| isSpeechToTextEnabled: props.textInput?.isSpeechToTextEnabled??uploadsConfig()?.isSpeechToTextEnabled??false | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against spreading undefined uploadsConfig
uploadsConfig() is undefined on the first render, so spreading it throws a TypeError. This breaks input rendering until the config arrives. Wrap the spread in a fallback object before decorating it.
- uploadsConfig={{- ...uploadsConfig(),- isSpeechToTextEnabled: props.textInput?.isSpeechToTextEnabled ?? uploadsConfig()?.isSpeechToTextEnabled ?? false- }}+ uploadsConfig={{+ ...(uploadsConfig() ??{}),+ isSpeechToTextEnabled: props.textInput?.isSpeechToTextEnabled ?? uploadsConfig()?.isSpeechToTextEnabled ?? false+ }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uploadsConfig={{ | |
| ...uploadsConfig(), | |
| isSpeechToTextEnabled: props.textInput?.isSpeechToTextEnabled??uploadsConfig()?.isSpeechToTextEnabled??false | |
| }} | |
| uploadsConfig={{ | |
| ...(uploadsConfig()??{}), | |
| isSpeechToTextEnabled: props.textInput?.isSpeechToTextEnabled??uploadsConfig()?.isSpeechToTextEnabled??false | |
| }} |
🤖 Prompt for AI Agents
In src/components/Bot.tsx around lines 2521 to 2524, spreading uploadsConfig() can throw when uploadsConfig() is undefined on first render; fix by falling back to an empty object before spreading (e.g., use (uploadsConfig() ??{}) as the spread source) then set isSpeechToTextEnabled using props.textInput?.isSpeechToTextEnabled ?? (uploadsConfig()?.isSpeechToTextEnabled ?? false) so the spread never receives undefined and the property still falls back to false. | constisFullChatbot=document.querySelector('flowise-fullchatbot')!==null; | ||
| constpaddingClass=isFullChatbot ? 'px-4' : 'px-12'; | ||
| constpaddingClass='px-2';// isFullChatbot ? 'px-2' : 'px-12'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused variable.
The isFullChatbot variable is computed but never used since the padding is now hardcoded. This is dead code that should be removed.
Apply this diff to remove the unused code:
- // Check if <flowise-fullchatbot> is present in the DOM- const isFullChatbot = document.querySelector('flowise-fullchatbot') !== null; const paddingClass = 'px-2'; // isFullChatbot ? 'px-2' : 'px-12';Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/buttons/SendButton.tsx around lines 32 to 33, remove the unused dead variable declaration `const isFullChatbot = document.querySelector('flowise-fullchatbot') !== null;` since padding is hardcoded; simply delete that line and leave the paddingClass assignment as-is, ensuring no other references to isFullChatbot exist in the file. | createEffect(()=>{ | ||
| if(isOpen()){ | ||
| document.addEventListener('click',handleOutsideClick); | ||
| return()=>document.removeEventListener('click',handleOutsideClick); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix leaked outside-click listener
createEffect in Solid does not use the return value for cleanup, so every open cycles adds another document listener that never detaches. Use onCleanup to unregister the handler and stop the leak.
-import{createSignal, Show, For, createEffect } from 'solid-js';+import{createSignal, Show, For, createEffect, onCleanup } from 'solid-js'; @@ createEffect(() =>{if (isOpen()){document.addEventListener('click', handleOutsideClick); - return () => document.removeEventListener('click', handleOutsideClick);+ onCleanup(() => document.removeEventListener('click', handleOutsideClick)); } });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| createEffect(()=>{ | |
| if(isOpen()){ | |
| document.addEventListener('click',handleOutsideClick); | |
| return()=>document.removeEventListener('click',handleOutsideClick); | |
| } | |
| }); | |
| createEffect(()=>{ | |
| if(isOpen()){ | |
| document.addEventListener('click',handleOutsideClick); | |
| onCleanup(()=>document.removeEventListener('click',handleOutsideClick)); | |
| } | |
| }); |
| ref={(el)=>{ | ||
| // 사용자 ref 호환 | ||
| constassign=(node: HTMLTextAreaElement|null)=>{ | ||
| if(typeofprops.ref==='function')props.ref(nodeasany); | ||
| elseif(props.ref)(props.refasany)=node; | ||
| }; | ||
| assign(el); | ||
| if(el){ | ||
| // keydown 캡처 단계에서 Backspace 전파 차단 | ||
| constkeydownCapture=(e: KeyboardEvent)=>{ | ||
| if(e.key==='Backspace'){ | ||
| // 포커스 보장 | ||
| if(document.activeElement!==el)el.focus(); | ||
| e.stopPropagation(); | ||
| // preventDefault는 하지 않음(삭제 동작 허용) | ||
| } | ||
| }; | ||
| el.addEventListener('keydown',keydownCapture,{capture: true}); | ||
| // beforeinput 캡처 단계에서 deleteContentBackward 전파 차단 | ||
| constbeforeInputCapture=(e: Event)=>{ | ||
| constie=easInputEvent; | ||
| if((ieasany).inputType==='deleteContentBackward'){ | ||
| e.stopPropagation(); | ||
| } | ||
| }; | ||
| el.addEventListener('beforeinput',beforeInputCaptureasEventListener,{capture: true}); | ||
| // 클린업 | ||
| return()=>{ | ||
| el.removeEventListener('keydown',keydownCapture,{capture: true}asany); | ||
| el.removeEventListener('beforeinput',beforeInputCaptureasEventListener,{capture: true}asany); | ||
| }; | ||
| } | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Memory leak from improper cleanup and incorrect ref assignment.
This ref callback implementation has critical issues:
Memory leak: SolidJS ref callbacks don't support returning cleanup functions like React's
useEffect. The cleanup function returned on lines 84-88 will never be called, so the event listeners added on lines 72 and 81 will never be removed. This causes a memory leak every time the component is mounted.Incorrect object ref assignment: Line 58 attempts
(props.ref as any) = node;which won't work for object refs. You cannot reassign a const reference; you need to assign to itscurrentproperty if it's a mutable ref object.Complexity: This adds the second and third layers of Backspace/deletion handling (capture-phase
keydownandbeforeinput), making the logic difficult to understand and maintain.
Solution: Use SolidJS's proper cleanup mechanism:
- return (- <textarea- ref={(el) =>{- // 사용자 ref 호환- const assign = (node: HTMLTextAreaElement | null) =>{- if (typeof props.ref === 'function') props.ref(node as any);- else if (props.ref) (props.ref as any) = node;- };- assign(el);-- if (el){- // keydown 캡처 단계에서 Backspace 전파 차단- const keydownCapture = (e: KeyboardEvent) =>{- if (e.key === 'Backspace'){- // 포커스 보장- if (document.activeElement !== el) el.focus();- e.stopPropagation();- // preventDefault는 하지 않음(삭제 동작 허용)- }- };- el.addEventListener('keydown', keydownCapture,{capture: true });-- // beforeinput 캡처 단계에서 deleteContentBackward 전파 차단- const beforeInputCapture = (e: Event) =>{- const ie = e as InputEvent;- if ((ie as any).inputType === 'deleteContentBackward'){- e.stopPropagation();- }- };- el.addEventListener('beforeinput', beforeInputCapture as EventListener,{capture: true });-- // 클린업- return () =>{- el.removeEventListener('keydown', keydownCapture,{capture: true } as any);- el.removeEventListener('beforeinput', beforeInputCapture as EventListener,{capture: true } as any);- };- }- }}+ let textareaRef: HTMLTextAreaElement | undefined;++ createEffect(() =>{+ const el = textareaRef;+ if (!el) return;++ // Capture-phase listener for Backspace+ const keydownCapture = (e: KeyboardEvent) =>{+ if (e.key === 'Backspace'){+ if (document.activeElement !== el) el.focus();+ e.stopPropagation();+ }+ };+ el.addEventListener('keydown', keydownCapture,{capture: true });++ // Capture-phase listener for deleteContentBackward+ const beforeInputCapture = (e: Event) =>{+ const ie = e as InputEvent;+ if (ie.inputType === 'deleteContentBackward'){+ e.stopPropagation();+ }+ };+ el.addEventListener('beforeinput', beforeInputCapture,{capture: true });++ // Proper cleanup in SolidJS+ onCleanup(() =>{+ el.removeEventListener('keydown', keydownCapture,{capture: true });+ el.removeEventListener('beforeinput', beforeInputCapture,{capture: true });+ });+ });++ return (+ <textarea+ ref={(el) =>{+ textareaRef = el;+ // Assign to user's ref+ if (typeof props.ref === 'function'){+ props.ref(el as any);+ } else if (props.ref && 'current' in props.ref){+ (props.ref as any).current = el;+ }+ }}Note: You'll need to import createEffect and onCleanup from 'solid-js'.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/inputs/textInput/components/ShortTextInput.tsx around lines 54-89: the ref callback currently returns a cleanup (which SolidJS never calls) and assigns object refs incorrectly, causing leaked event listeners and broken object-ref behavior. Change the ref callback to only set a local element variable and forward refs by calling props.ref(node) if it's a function or setting (props.ref as any).current = node if it's an object ref; then move the addEventListener logic into a Solid effect (createEffect) that watches the local element and use onCleanup to remove the keydown and beforeinput listeners when the element changes/unmounts. Ensure you import createEffect and onCleanup from 'solid-js' and remove the returned cleanup from the ref callback. | consthideButton=(bubbleProps.themeasany)?.button?.hideButton??false; | ||
| constexternalTriggerElementId: string|undefined=(bubbleProps.themeasany)?.button?.externalTriggerElementId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Type assertions bypass type safety.
Using (bubbleProps.theme as any) to access hideButton and externalTriggerElementId indicates these properties are missing from the theme type definition. This bypasses TypeScript's type checking and can lead to runtime errors.
Update the theme type definition to include these properties:
// In the theme types file (e.g., src/features/bubble/types.ts)interfaceButtonTheme{// ... existing propertieshideButton?: boolean;externalTriggerElementId?: string;}Then remove the type assertions:
- const hideButton = (bubbleProps.theme as any)?.button?.hideButton ?? false;- const externalTriggerElementId: string | undefined = (bubbleProps.theme as any)?.button?.externalTriggerElementId;+ const hideButton = bubbleProps.theme?.button?.hideButton ?? false;+ const externalTriggerElementId = bubbleProps.theme?.button?.externalTriggerElementId;🤖 Prompt for AI Agents
In src/features/bubble/components/Bubble.tsx around lines 57-58, the code uses (bubbleProps.theme as any) to access button.hideButton and button.externalTriggerElementId which bypasses TypeScript safety; update the theme types (e.g., src/features/bubble/types.ts) to add hideButton?: boolean and externalTriggerElementId?: string on the ButtonTheme interface (or the appropriate theme interface), ensure BubbleProps.theme is typed to include that ButtonTheme, then remove the type assertions in Bubble.tsx and access bubbleProps.theme.button.hideButton and bubbleProps.theme.button.externalTriggerElementId directly. | // 영어, 숫자, 일반 특수문자: 1.0 | ||
| return1.5; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix inconsistency between comment and implementation.
The comment on line 75 states that English, numbers, and general special characters should have a width of 1.0, but line 76 returns 1.5. This inconsistency will cause incorrect width calculations for ASCII characters.
Apply this diff to align the implementation with the documented behavior:
- // 영어, 숫자, 일반 특수문자: 1.0- return 1.5;+ // 영어, 숫자, 일반 특수문자: 1.0+ return 1.0;Alternatively, if 1.5 is the intended value, update the comment to reflect the actual behavior.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 영어, 숫자, 일반 특수문자: 1.0 | |
| return1.5; | |
| } | |
| // 영어, 숫자, 일반 특수문자: 1.0 | |
| return1.0; | |
| } |
🤖 Prompt for AI Agents
In src/utils/textTruncator.ts around lines 75 to 77, the comment says English/numeric/general special characters have width 1.0 but the function returns 1.5; to fix, make the implementation match the comment by changing the returned value from 1.5 to 1.0, or if 1.5 is intended, update the comment to state that ASCII characters are treated as width 1.5—pick one and keep comment and return value consistent.
Summary by CodeRabbit
Release Notes
New Features
UI/UX Improvements
Chores