mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Feature: embedded chat theme (#11581)
### What problem does this PR solve? This PR closing feature request #11286. It implements ability to choose the background theme of the _Full screen chat_ which is Embed into webpage. Looks like that: <img width="501" height="349" alt="image" src="https://github.com/user-attachments/assets/e5fdfb14-9ed9-43bb-a40d-4b580985b9d4" /> It works similar to `Locale`, using url parameter to set the theme. if the parameter is invalid then is using the default theme. ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: Your Name <you@example.com>
This commit is contained in:
@ -22,6 +22,7 @@ import { SharedFrom } from '@/constants/chat';
|
|||||||
import {
|
import {
|
||||||
LanguageAbbreviation,
|
LanguageAbbreviation,
|
||||||
LanguageAbbreviationMap,
|
LanguageAbbreviationMap,
|
||||||
|
ThemeEnum,
|
||||||
} from '@/constants/common';
|
} from '@/constants/common';
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
@ -36,6 +37,7 @@ const FormSchema = z.object({
|
|||||||
locale: z.string(),
|
locale: z.string(),
|
||||||
embedType: z.enum(['fullscreen', 'widget']),
|
embedType: z.enum(['fullscreen', 'widget']),
|
||||||
enableStreaming: z.boolean(),
|
enableStreaming: z.boolean(),
|
||||||
|
theme: z.enum([ThemeEnum.Light, ThemeEnum.Dark]),
|
||||||
});
|
});
|
||||||
|
|
||||||
type IProps = IModalProps<any> & {
|
type IProps = IModalProps<any> & {
|
||||||
@ -61,6 +63,7 @@ function EmbedDialog({
|
|||||||
locale: '',
|
locale: '',
|
||||||
embedType: 'fullscreen' as const,
|
embedType: 'fullscreen' as const,
|
||||||
enableStreaming: false,
|
enableStreaming: false,
|
||||||
|
theme: ThemeEnum.Light,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -74,7 +77,7 @@ function EmbedDialog({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const generateIframeSrc = useCallback(() => {
|
const generateIframeSrc = useCallback(() => {
|
||||||
const { visibleAvatar, locale, embedType, enableStreaming } = values;
|
const { visibleAvatar, locale, embedType, enableStreaming, theme } = values;
|
||||||
const baseRoute =
|
const baseRoute =
|
||||||
embedType === 'widget'
|
embedType === 'widget'
|
||||||
? Routes.ChatWidget
|
? Routes.ChatWidget
|
||||||
@ -91,6 +94,9 @@ function EmbedDialog({
|
|||||||
if (enableStreaming) {
|
if (enableStreaming) {
|
||||||
src += '&streaming=true';
|
src += '&streaming=true';
|
||||||
}
|
}
|
||||||
|
if (theme && embedType === 'fullscreen') {
|
||||||
|
src += `&theme=${theme}`;
|
||||||
|
}
|
||||||
return src;
|
return src;
|
||||||
}, [beta, from, token, values]);
|
}, [beta, from, token, values]);
|
||||||
|
|
||||||
@ -181,6 +187,41 @@ function EmbedDialog({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{values.embedType === 'fullscreen' && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="theme"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Theme</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroup
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value}
|
||||||
|
className="flex flex-row space-x-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem
|
||||||
|
value={ThemeEnum.Light}
|
||||||
|
id="light"
|
||||||
|
/>
|
||||||
|
<Label htmlFor="light" className="text-sm">
|
||||||
|
Light
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value={ThemeEnum.Dark} id="dark" />
|
||||||
|
<Label htmlFor="dark" className="text-sm">
|
||||||
|
Dark
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="visibleAvatar"
|
name="visibleAvatar"
|
||||||
|
|||||||
@ -71,3 +71,13 @@ export function useSwitchToDarkThemeOnMount() {
|
|||||||
setTheme(ThemeEnum.Dark);
|
setTheme(ThemeEnum.Dark);
|
||||||
}, [setTheme]);
|
}, [setTheme]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useSyncThemeFromParams(theme: string | null) {
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (theme && (theme === ThemeEnum.Light || theme === ThemeEnum.Dark)) {
|
||||||
|
setTheme(theme as ThemeEnum);
|
||||||
|
}
|
||||||
|
}, [theme, setTheme]);
|
||||||
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export const useGetSharedChatSearchParams = () => {
|
|||||||
from: searchParams.get('from') as SharedFrom,
|
from: searchParams.get('from') as SharedFrom,
|
||||||
sharedId: searchParams.get('shared_id'),
|
sharedId: searchParams.get('shared_id'),
|
||||||
locale: searchParams.get('locale'),
|
locale: searchParams.get('locale'),
|
||||||
|
theme: searchParams.get('theme'),
|
||||||
data: data,
|
data: data,
|
||||||
visibleAvatar: searchParams.get('visible_avatar')
|
visibleAvatar: searchParams.get('visible_avatar')
|
||||||
? searchParams.get('visible_avatar') !== '1'
|
? searchParams.get('visible_avatar') !== '1'
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { NextMessageInput } from '@/components/message-input/next';
|
|||||||
import MessageItem from '@/components/next-message-item';
|
import MessageItem from '@/components/next-message-item';
|
||||||
import PdfSheet from '@/components/pdf-drawer';
|
import PdfSheet from '@/components/pdf-drawer';
|
||||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||||
|
import { useSyncThemeFromParams } from '@/components/theme-provider';
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { useUploadCanvasFileWithProgress } from '@/hooks/use-agent-request';
|
import { useUploadCanvasFileWithProgress } from '@/hooks/use-agent-request';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@ -25,8 +26,10 @@ const ChatContainer = () => {
|
|||||||
const {
|
const {
|
||||||
sharedId: conversationId,
|
sharedId: conversationId,
|
||||||
locale,
|
locale,
|
||||||
|
theme,
|
||||||
visibleAvatar,
|
visibleAvatar,
|
||||||
} = useGetSharedChatSearchParams();
|
} = useGetSharedChatSearchParams();
|
||||||
|
useSyncThemeFromParams(theme);
|
||||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||||
useClickDrawer();
|
useClickDrawer();
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ export const useGetSharedChatSearchParams = () => {
|
|||||||
from: searchParams.get('from') as SharedFrom,
|
from: searchParams.get('from') as SharedFrom,
|
||||||
sharedId: searchParams.get('shared_id'),
|
sharedId: searchParams.get('shared_id'),
|
||||||
locale: searchParams.get('locale'),
|
locale: searchParams.get('locale'),
|
||||||
|
theme: searchParams.get('theme'),
|
||||||
data: data,
|
data: data,
|
||||||
visibleAvatar: searchParams.get('visible_avatar')
|
visibleAvatar: searchParams.get('visible_avatar')
|
||||||
? searchParams.get('visible_avatar') !== '1'
|
? searchParams.get('visible_avatar') !== '1'
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { NextMessageInput } from '@/components/message-input/next';
|
|||||||
import MessageItem from '@/components/message-item';
|
import MessageItem from '@/components/message-item';
|
||||||
import PdfSheet from '@/components/pdf-drawer';
|
import PdfSheet from '@/components/pdf-drawer';
|
||||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||||
|
import { useSyncThemeFromParams } from '@/components/theme-provider';
|
||||||
import { MessageType, SharedFrom } from '@/constants/chat';
|
import { MessageType, SharedFrom } from '@/constants/chat';
|
||||||
import { useFetchNextConversationSSE } from '@/hooks/chat-hooks';
|
import { useFetchNextConversationSSE } from '@/hooks/chat-hooks';
|
||||||
import { useFetchFlowSSE } from '@/hooks/flow-hooks';
|
import { useFetchFlowSSE } from '@/hooks/flow-hooks';
|
||||||
@ -22,8 +23,10 @@ const ChatContainer = () => {
|
|||||||
sharedId: conversationId,
|
sharedId: conversationId,
|
||||||
from,
|
from,
|
||||||
locale,
|
locale,
|
||||||
|
theme,
|
||||||
visibleAvatar,
|
visibleAvatar,
|
||||||
} = useGetSharedChatSearchParams();
|
} = useGetSharedChatSearchParams();
|
||||||
|
useSyncThemeFromParams(theme);
|
||||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||||
useClickDrawer();
|
useClickDrawer();
|
||||||
|
|
||||||
@ -52,6 +55,7 @@ const ChatContainer = () => {
|
|||||||
i18n.changeLanguage(locale);
|
i18n.changeLanguage(locale);
|
||||||
}
|
}
|
||||||
}, [locale, visibleAvatar]);
|
}, [locale, visibleAvatar]);
|
||||||
|
|
||||||
const { data: avatarData } = useFetchAvatar();
|
const { data: avatarData } = useFetchAvatar();
|
||||||
|
|
||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user