-
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
Conversation
Ksm/initial upload
출처 박스 변경. 디자인 반영.
refresh button, pdf icon
combo config
…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.
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(); // CopyToClipboardButton title={t('buttons.copyToClipboard')} // ThumbsUpButton title={t('buttons.thumbsUp')} // ThumbsDownButton title={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'; // hypothetical const FeedbackContentDialog = (props: FeedbackContentDialogProps) => { const { t } = useI18n(); return ( // ... <span>{t('feedback.title')}</span> // ... <textarea placeholder={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', () => { const result = truncateTextByWidth('Hello World!', 10); expect(getTextWidth(result)).toBeLessThanOrEqual(10); }); it('should handle Korean text', () => { const result = 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', () => { const width = 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:
<img style={{ ... }} src={`data:image/png;base64,${props.imageSrc}`} alt="source" onError={(e) => { // Hide broken image or show placeholder (e.target as HTMLImageElement).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.
| } | ||
| > | ||
| <img src={avatarSrc()} alt="Bot avatar" class="rounded-full object-cover w-full h-full" /> | ||
| <img src={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.
| <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" /> |
🤖 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.
| const isFullChatbot = document.querySelector('flowise-fullchatbot') !== null; | ||
| const paddingClass = isFullChatbot ? 'px-4' : 'px-12'; | ||
| const paddingClass = '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 호환 | ||
| 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); | ||
| }; | ||
| } | ||
| }} |
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.
| const hideButton = (bubbleProps.theme as any)?.button?.hideButton ?? false; | ||
| const externalTriggerElementId: string | undefined = (bubbleProps.theme as any)?.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)
interface ButtonTheme {
// ... existing properties
hideButton?: 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 | ||
| return 1.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 | |
| return 1.5; | |
| } | |
| // 영어, 숫자, 일반 특수문자: 1.0 | |
| return 1.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