Skip to content

Commit 150e7dc

Browse files
committed
pr
1 parent 6ab8785 commit 150e7dc

File tree

12 files changed

+169025
-93
lines changed

12 files changed

+169025
-93
lines changed

dist/components/bubbles/BotBubble.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FeedbackRatingType } from '@/queries/sendMessageQuery';
12
import { IAction, MessageType } from '../Bot';
23
import { DateTimeToggleTheme } from '@/features/bubble/types';
34
type Props = {
@@ -23,6 +24,8 @@ type Props = {
2324
handleSourceDocumentsClick: (src: any) => void;
2425
onRegenerateResponse?: () => void;
2526
onMessageRendered?: () => void;
27+
messageRatings?: Record<string, FeedbackRatingType>;
28+
onMessageRatingChange?: (messageId: string, rating: FeedbackRatingType) => void;
2629
isTTSEnabled?: boolean;
2730
isTTSLoading?: Record<string, boolean>;
2831
isTTSPlaying?: Record<string, boolean>;

dist/components/bubbles/BotBubble.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export declare const DownloadFileIcon: () => import("solid-js").JSX.Element;
2+
//# sourceMappingURL=DownloadFileIcon.d.ts.map

dist/components/icons/DownloadFileIcon.d.ts.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/components/icons/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export * from './SparklesIcon';
1414
export * from './VolumeIcon';
1515
export * from './SquareStopIcon';
1616
export * from './ChevronDownIcon';
17+
export * from './DownloadFileIcon';
1718
//# sourceMappingURL=index.d.ts.map

dist/components/icons/index.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/web.js

Lines changed: 84460 additions & 1 deletion
Large diffs are not rendered by default.

dist/web.umd.js

Lines changed: 84468 additions & 1 deletion
Large diffs are not rendered by default.

src/assets/index.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,19 @@ textarea {
231231
white-space: normal;
232232
}
233233

234+
.bot-markdown-content :is(a, h1, h2, h3, h4, h5, h6, strong, em, blockquote, li) {
235+
color: var(--bot-markdown-text-color, var(--chatbot-host-bubble-color));
236+
}
237+
238+
.bot-markdown-content pre,
239+
.bot-markdown-content pre code {
240+
color: var(--bot-markdown-code-color, #ffffff);
241+
}
242+
243+
.bot-markdown-content code:not(pre code) {
244+
color: var(--bot-markdown-inline-code-color, #4caf50);
245+
}
246+
234247
.chatbot-host-bubble > .bubble-typing {
235248
background-color: #f7f8ff;
236249
border: var(--chatbot-host-bubble-border);

src/components/bubbles/BotBubble.tsx

Lines changed: 55 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { RegenerateResponseButton } from '../buttons/RegenerateResponseButton';
99
import { TTSButton } from '../buttons/TTSButton';
1010
import FeedbackContentDialog from '../FeedbackContentDialog';
1111
import { AgentReasoningBubble } from './AgentReasoningBubble';
12-
import { TickIcon, XIcon } from '../icons';
12+
import { DownloadFileIcon, TickIcon, XIcon } from '../icons';
1313
import { SourceBubble } from '../bubbles/SourceBubble';
1414
import { DateTimeToggleTheme } from '@/features/bubble/types';
1515
import { WorkflowTreeView } from '../treeview/WorkflowTreeView';
@@ -38,6 +38,8 @@ type Props = {
3838
handleSourceDocumentsClick: (src: any) => void;
3939
onRegenerateResponse?: () => void;
4040
onMessageRendered?: () => void;
41+
messageRatings?: Record<string, FeedbackRatingType>;
42+
onMessageRatingChange?: (messageId: string, rating: FeedbackRatingType) => void;
4143
// TTS props
4244
isTTSEnabled?: boolean;
4345
isTTSLoading?: Record<string, boolean>;
@@ -56,93 +58,52 @@ const defaultFeedbackColor = '#3B81F6';
5658
export const BotBubble = (props: Props) => {
5759
let botDetailsEl: HTMLDetailsElement | undefined;
5860

59-
const DownloadFileIcon = () => (
60-
<svg
61-
xmlns="http://www.w3.org/2000/svg"
62-
class="icon icon-tabler icon-tabler-download"
63-
width="24"
64-
height="24"
65-
viewBox="0 0 24 24"
66-
stroke-width="2"
67-
stroke="#ffffff"
68-
fill="none"
69-
stroke-linecap="round"
70-
stroke-linejoin="round"
71-
>
72-
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
73-
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />
74-
<path d="M7 11l5 5l5 -5" />
75-
<path d="M12 4l0 12" />
76-
</svg>
77-
);
78-
7961
Marked.setOptions({ isNoP: true, sanitize: props.renderHTML !== undefined ? !props.renderHTML : true });
8062

81-
const [rating, setRating] = createSignal('');
8263
const [feedbackId, setFeedbackId] = createSignal('');
8364
const [showFeedbackContentDialog, setShowFeedbackContentModal] = createSignal(false);
8465
const [copiedMessage, setCopiedMessage] = createSignal(false);
85-
const [thumbsUpColor, setThumbsUpColor] = createSignal(props.feedbackColor ?? defaultFeedbackColor); // default color
86-
const [thumbsDownColor, setThumbsDownColor] = createSignal(props.feedbackColor ?? defaultFeedbackColor); // default color
8766

8867
// Store a reference to the bot message element for the copyMessageToClipboard function
8968
const [botMessageElement, setBotMessageElement] = createSignal<HTMLElement | null>(null);
9069

91-
const applyStyles = (el: HTMLElement) => {
92-
const textColor = props.textColor ?? defaultTextColor;
93-
el.querySelectorAll('a, h1, h2, h3, h4, h5, h6, strong, em, blockquote, li').forEach((element) => {
94-
(element as HTMLElement).style.color = textColor;
95-
});
96-
el.querySelectorAll('pre').forEach((element) => {
97-
(element as HTMLElement).style.color = '#FFFFFF';
98-
element.querySelectorAll('code').forEach((codeElement) => {
99-
(codeElement as HTMLElement).style.color = '#FFFFFF';
100-
});
101-
});
102-
el.querySelectorAll('code:not(pre code)').forEach((element) => {
103-
(element as HTMLElement).style.color = '#4CAF50';
104-
});
105-
el.querySelectorAll('a').forEach((link) => {
106-
link.target = '_blank';
107-
});
70+
const currentRating = () => {
71+
const messageId = props.message.messageId;
72+
if (!messageId) return props.message.rating ?? '';
73+
return props.messageRatings?.[messageId] ?? props.message.rating ?? '';
74+
};
75+
76+
const thumbsUpColor = () => (currentRating() === 'THUMBS_UP' ? '#006400' : props.feedbackColor ?? defaultFeedbackColor);
77+
const thumbsDownColor = () => (currentRating() === 'THUMBS_DOWN' ? '#8B0000' : props.feedbackColor ?? defaultFeedbackColor);
78+
79+
const renderMarkdownHtml = (content: string) => {
80+
const html = Marked.parse(content);
81+
return html.replace(/<a(?![^>]*\btarget=)([^>]*)>/g, '<a target="_blank" rel="noopener noreferrer"$1>');
10882
};
10983

11084
const setBotMessageRef = (el: HTMLSpanElement | null) => {
11185
setBotMessageElement(el);
11286
};
11387

88+
const notifyMessageRendered = () => {
89+
props.onMessageRendered?.();
90+
};
91+
11492
createEffect(() => {
11593
const el = botMessageElement();
11694
const message = props.message.message ?? '';
11795
if (!el) return;
11896

11997
// Update innerHTML synchronously so the DOM reflects the correct height
12098
// before any scroll logic runs — avoids async mismatch that causes jumping.
121-
el.innerHTML = Marked.parse(message);
122-
applyStyles(el);
123-
124-
// Only wire image callbacks and notify after streaming ends.
125-
if (!props.isLoading) {
126-
el.querySelectorAll('img').forEach((img) => {
127-
if ((img as HTMLImageElement).complete) return;
128-
img.addEventListener('load', () => props.onMessageRendered?.(), { once: true });
129-
img.addEventListener('error', () => props.onMessageRendered?.(), { once: true });
130-
});
131-
props.onMessageRendered?.();
132-
}
133-
});
99+
el.innerHTML = renderMarkdownHtml(message);
134100

135-
createEffect(() => {
136-
const nextRating = props.message.rating;
137-
if (!nextRating) return;
138-
setRating(nextRating);
139-
if (nextRating === 'THUMBS_UP') {
140-
setThumbsUpColor('#006400');
141-
return;
142-
}
143-
if (nextRating === 'THUMBS_DOWN') {
144-
setThumbsDownColor('#8B0000');
145-
}
101+
el.querySelectorAll('img').forEach((img) => {
102+
if ((img as HTMLImageElement).complete) return;
103+
img.addEventListener('load', notifyMessageRendered, { once: true });
104+
img.addEventListener('error', notifyMessageRendered, { once: true });
105+
});
106+
notifyMessageRendered();
146107
});
147108

148109
createEffect(() => {
@@ -221,11 +182,13 @@ export const BotBubble = (props: Props) => {
221182
};
222183

223184
const onThumbsUpClick = async () => {
224-
if (rating() === '') {
185+
if (currentRating() === '') {
186+
const messageId = props.message?.messageId;
187+
if (!messageId) return;
225188
const body = {
226189
chatflowid: props.chatflowid,
227190
chatId: props.chatId,
228-
messageId: props.message?.messageId as string,
191+
messageId,
229192
rating: 'THUMBS_UP' as FeedbackRatingType,
230193
content: '',
231194
};
@@ -240,22 +203,22 @@ export const BotBubble = (props: Props) => {
240203
const data = result.data as any;
241204
let id = '';
242205
if (data && data.id) id = data.id;
243-
setRating('THUMBS_UP');
206+
props.onMessageRatingChange?.(messageId, 'THUMBS_UP');
244207
setFeedbackId(id);
245208
setShowFeedbackContentModal(true);
246-
// update the thumbs up color state
247-
setThumbsUpColor('#006400');
248209
saveToLocalStorage('THUMBS_UP');
249210
}
250211
}
251212
};
252213

253214
const onThumbsDownClick = async () => {
254-
if (rating() === '') {
215+
if (currentRating() === '') {
216+
const messageId = props.message?.messageId;
217+
if (!messageId) return;
255218
const body = {
256219
chatflowid: props.chatflowid,
257220
chatId: props.chatId,
258-
messageId: props.message?.messageId as string,
221+
messageId,
259222
rating: 'THUMBS_DOWN' as FeedbackRatingType,
260223
content: '',
261224
};
@@ -270,11 +233,9 @@ export const BotBubble = (props: Props) => {
270233
const data = result.data as any;
271234
let id = '';
272235
if (data && data.id) id = data.id;
273-
setRating('THUMBS_DOWN');
236+
props.onMessageRatingChange?.(messageId, 'THUMBS_DOWN');
274237
setFeedbackId(id);
275238
setShowFeedbackContentModal(true);
276-
// update the thumbs down color state
277-
setThumbsDownColor('#8B0000');
278239
saveToLocalStorage('THUMBS_DOWN');
279240
}
280241
}
@@ -312,11 +273,6 @@ export const BotBubble = (props: Props) => {
312273
});
313274

314275
const renderArtifacts = (item: Partial<FileUpload>) => {
315-
// Instead of onMount, we'll use a callback ref to apply styles
316-
const setArtifactRef = (el: HTMLSpanElement) => {
317-
if (el) applyStyles(el);
318-
};
319-
320276
return (
321277
<>
322278
<Show when={item.type === 'png' || item.type === 'jpeg'}>
@@ -344,14 +300,16 @@ export const BotBubble = (props: Props) => {
344300
</Show>
345301
<Show when={item.type !== 'png' && item.type !== 'jpeg' && item.type !== 'html'}>
346302
<span
347-
ref={setArtifactRef}
348-
innerHTML={Marked.parse(item.data as string)}
349-
class="prose"
303+
innerHTML={renderMarkdownHtml(item.data as string)}
304+
class="prose bot-markdown-content"
350305
style={{
351306
'background-color': props.backgroundColor ?? defaultBackgroundColor,
352307
color: props.textColor ?? defaultTextColor,
353308
'border-radius': '6px',
354309
'font-size': props.fontSize ? `${props.fontSize}px` : `${defaultFontSize}px`,
310+
'--bot-markdown-text-color': props.textColor ?? defaultTextColor,
311+
'--bot-markdown-code-color': '#FFFFFF',
312+
'--bot-markdown-inline-code-color': '#4CAF50',
355313
}}
356314
/>
357315
</Show>
@@ -478,13 +436,16 @@ export const BotBubble = (props: Props) => {
478436
<>
479437
<span
480438
ref={setBotMessageRef}
481-
class="px-4 py-2 ml-2 max-w-full chatbot-host-bubble prose"
439+
class="px-4 py-2 ml-2 max-w-full chatbot-host-bubble prose bot-markdown-content"
482440
data-testid="host-bubble"
483441
style={{
484442
'background-color': props.backgroundColor ?? defaultBackgroundColor,
485443
color: props.textColor ?? defaultTextColor,
486444
'border-radius': '6px',
487445
'font-size': props.fontSize ? `${props.fontSize}px` : `${defaultFontSize}px`,
446+
'--bot-markdown-text-color': props.textColor ?? defaultTextColor,
447+
'--bot-markdown-code-color': '#FFFFFF',
448+
'--bot-markdown-inline-code-color': '#4CAF50',
488449
}}
489450
/>
490451
<For each={props.fileAnnotations || []}>
@@ -611,14 +572,19 @@ export const BotBubble = (props: Props) => {
611572
Copied!
612573
</div>
613574
</Show>
614-
{rating() === '' || rating() === 'THUMBS_UP' ? (
615-
<ThumbsUpButton feedbackColor={thumbsUpColor()} isDisabled={rating() === 'THUMBS_UP'} rating={rating()} onClick={onThumbsUpClick} />
575+
{currentRating() === '' || currentRating() === 'THUMBS_UP' ? (
576+
<ThumbsUpButton
577+
feedbackColor={thumbsUpColor()}
578+
isDisabled={currentRating() === 'THUMBS_UP'}
579+
rating={currentRating()}
580+
onClick={onThumbsUpClick}
581+
/>
616582
) : null}
617-
{rating() === '' || rating() === 'THUMBS_DOWN' ? (
583+
{currentRating() === '' || currentRating() === 'THUMBS_DOWN' ? (
618584
<ThumbsDownButton
619585
feedbackColor={thumbsDownColor()}
620-
isDisabled={rating() === 'THUMBS_DOWN'}
621-
rating={rating()}
586+
isDisabled={currentRating() === 'THUMBS_DOWN'}
587+
rating={currentRating()}
622588
onClick={onThumbsDownClick}
623589
/>
624590
) : null}

0 commit comments

Comments
 (0)