mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-30 23:26:36 +08:00
Theme switch support (#3568)
### What problem does this PR solve? - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: Yingfeng <yingfeng.zhang@gmail.com> Co-authored-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
@ -16,4 +16,5 @@
|
||||
.apiLinkText {
|
||||
.linkText();
|
||||
margin: 0 !important;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
@ -4,5 +4,5 @@
|
||||
|
||||
.codeText {
|
||||
padding: 10px;
|
||||
background-color: #e8e8ea;
|
||||
background-color: #ffffff09;
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ export const EditableCell: React.FC<EditableCellProps> = ({
|
||||
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
|
||||
</Form.Item>
|
||||
) : (
|
||||
<div onClick={toggleEdit} className="truncate">
|
||||
<div onClick={toggleEdit} className="editable-cell-value-wrap">
|
||||
<Text>{children}</Text>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -15,5 +15,5 @@
|
||||
white-space: break-spaces;
|
||||
background-color: rgba(129, 139, 152, 0.12);
|
||||
border-radius: 4px;
|
||||
color: rgb(31, 35, 40);
|
||||
color: var(--ant-color-text-base);
|
||||
}
|
||||
|
||||
@ -312,81 +312,97 @@ function fallbackRender({ error }: FallbackProps) {
|
||||
const IndentedTree = ({ data, show, style = {} }: IProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const graphRef = useRef<Graph | null>(null);
|
||||
|
||||
const render = useCallback(async (data: TreeData) => {
|
||||
const graph: Graph = new Graph({
|
||||
container: containerRef.current!,
|
||||
x: 60,
|
||||
node: {
|
||||
type: 'indented',
|
||||
style: {
|
||||
size: (d) => [d.id.length * 6 + 10, 20],
|
||||
labelBackground: (datum) => datum.id === rootId,
|
||||
labelBackgroundRadius: 0,
|
||||
labelBackgroundFill: '#576286',
|
||||
labelFill: (datum) => (datum.id === rootId ? '#fff' : '#666'),
|
||||
labelText: (d) => d.style?.labelText || d.id,
|
||||
labelTextAlign: (datum) => (datum.id === rootId ? 'center' : 'left'),
|
||||
labelTextBaseline: 'top',
|
||||
color: (datum: any) => {
|
||||
const depth = graph.getAncestorsData(datum.id, 'tree').length - 1;
|
||||
return COLORS[depth % COLORS.length] || '#576286';
|
||||
},
|
||||
},
|
||||
state: {
|
||||
selected: {
|
||||
lineWidth: 0,
|
||||
labelFill: '#40A8FF',
|
||||
labelBackground: true,
|
||||
labelFontWeight: 'normal',
|
||||
labelBackgroundFill: '#e8f7ff',
|
||||
labelBackgroundRadius: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
edge: {
|
||||
type: 'indented',
|
||||
style: {
|
||||
radius: 16,
|
||||
lineWidth: 2,
|
||||
sourcePort: 'out',
|
||||
targetPort: 'in',
|
||||
stroke: (datum: any) => {
|
||||
const depth = graph.getAncestorsData(datum.source, 'tree').length;
|
||||
return COLORS[depth % COLORS.length] || 'black';
|
||||
},
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
type: 'indented',
|
||||
direction: 'LR',
|
||||
isHorizontal: true,
|
||||
indent: 40,
|
||||
getHeight: () => 20,
|
||||
getVGap: () => 10,
|
||||
},
|
||||
behaviors: [
|
||||
'scroll-canvas',
|
||||
'collapse-expand-tree',
|
||||
{
|
||||
type: 'click-select',
|
||||
enable: (event: any) =>
|
||||
event.targetType === 'node' && event.target.id !== rootId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (graphRef.current) {
|
||||
graphRef.current.destroy();
|
||||
const assignIds = React.useCallback(function assignIds(
|
||||
node: TreeData,
|
||||
parentId: string = '',
|
||||
index = 0,
|
||||
) {
|
||||
if (!node.id) node.id = parentId ? `${parentId}-${index}` : 'root';
|
||||
if (node.children) {
|
||||
node.children.forEach((child, idx) => assignIds(child, node.id, idx));
|
||||
}
|
||||
|
||||
graphRef.current = graph;
|
||||
|
||||
graph?.setData(treeToGraphData(data));
|
||||
|
||||
graph?.render();
|
||||
}, []);
|
||||
|
||||
const render = useCallback(
|
||||
async (data: TreeData) => {
|
||||
const graph: Graph = new Graph({
|
||||
container: containerRef.current!,
|
||||
x: 60,
|
||||
node: {
|
||||
type: 'indented',
|
||||
style: {
|
||||
size: (d) => [d.id.length * 6 + 10, 20],
|
||||
labelBackground: (datum) => datum.id === rootId,
|
||||
labelBackgroundRadius: 0,
|
||||
labelBackgroundFill: '#576286',
|
||||
labelFill: (datum) => (datum.id === rootId ? '#fff' : '#666'),
|
||||
labelText: (d) => d.style?.labelText || d.id,
|
||||
labelTextAlign: (datum) =>
|
||||
datum.id === rootId ? 'center' : 'left',
|
||||
labelTextBaseline: 'top',
|
||||
color: (datum: any) => {
|
||||
const depth = graph.getAncestorsData(datum.id, 'tree').length - 1;
|
||||
return COLORS[depth % COLORS.length] || '#576286';
|
||||
},
|
||||
},
|
||||
state: {
|
||||
selected: {
|
||||
lineWidth: 0,
|
||||
labelFill: '#40A8FF',
|
||||
labelBackground: true,
|
||||
labelFontWeight: 'normal',
|
||||
labelBackgroundFill: '#e8f7ff',
|
||||
labelBackgroundRadius: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
edge: {
|
||||
type: 'indented',
|
||||
style: {
|
||||
radius: 16,
|
||||
lineWidth: 2,
|
||||
sourcePort: 'out',
|
||||
targetPort: 'in',
|
||||
stroke: (datum: any) => {
|
||||
const depth = graph.getAncestorsData(datum.source, 'tree').length;
|
||||
return COLORS[depth % COLORS.length] || 'black';
|
||||
},
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
type: 'indented',
|
||||
direction: 'LR',
|
||||
isHorizontal: true,
|
||||
indent: 40,
|
||||
getHeight: () => 20,
|
||||
getVGap: () => 10,
|
||||
},
|
||||
behaviors: [
|
||||
'scroll-canvas',
|
||||
'collapse-expand-tree',
|
||||
{
|
||||
type: 'click-select',
|
||||
enable: (event: any) =>
|
||||
event.targetType === 'node' && event.target.id !== rootId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (graphRef.current) {
|
||||
graphRef.current.destroy();
|
||||
}
|
||||
|
||||
graphRef.current = graph;
|
||||
|
||||
assignIds(data);
|
||||
|
||||
graph?.setData(treeToGraphData(data));
|
||||
|
||||
graph?.render();
|
||||
},
|
||||
[assignIds],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(data)) {
|
||||
render(data);
|
||||
|
||||
@ -269,7 +269,7 @@ const LlmSettingItems = ({ prefix, formItemLayout = {} }: IProps) => {
|
||||
>
|
||||
<Slider
|
||||
className={styles.variableSlider}
|
||||
max={8192}
|
||||
max={128000}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Form.Item>
|
||||
@ -278,7 +278,7 @@ const LlmSettingItems = ({ prefix, formItemLayout = {} }: IProps) => {
|
||||
<InputNumber
|
||||
disabled={disabled}
|
||||
className={styles.sliderInputNumber}
|
||||
max={8192}
|
||||
max={128000}
|
||||
min={0}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.messageInputWrapper {
|
||||
margin-right: 20px;
|
||||
background-color: #f5f5f8;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
:global(.ant-input-affix-wrapper) {
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
} from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import get from 'lodash/get';
|
||||
import { Paperclip } from 'lucide-react';
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
memo,
|
||||
@ -36,7 +37,6 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import FileIcon from '../file-icon';
|
||||
import SvgIcon from '../svg-icon';
|
||||
import styles from './index.less';
|
||||
|
||||
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||
@ -98,7 +98,6 @@ const MessageInput = ({
|
||||
const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds();
|
||||
const { uploadAndParseDocument } = useUploadAndParseDocument(uploadMethod);
|
||||
const conversationIdRef = useRef(conversationId);
|
||||
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
|
||||
const handlePreview = async (file: UploadFile) => {
|
||||
@ -225,14 +224,7 @@ const MessageInput = ({
|
||||
<Button
|
||||
type={'text'}
|
||||
disabled={disabled}
|
||||
icon={
|
||||
<SvgIcon
|
||||
name="paper-clip"
|
||||
width={18}
|
||||
height={22}
|
||||
disabled={disabled}
|
||||
></SvgIcon>
|
||||
}
|
||||
icon={<Paperclip></Paperclip>}
|
||||
></Button>
|
||||
</Upload>
|
||||
)}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { PromptIcon } from '@/assets/icon/Icon';
|
||||
import CopyToClipboard from '@/components/copy-to-clipboard';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||
@ -12,7 +13,6 @@ import {
|
||||
import { Radio, Tooltip } from 'antd';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import SvgIcon from '../svg-icon';
|
||||
import FeedbackModal from './feedback-modal';
|
||||
import { useRemoveMessage, useSendFeedback, useSpeech } from './hooks';
|
||||
import PromptModal from './prompt-modal';
|
||||
@ -70,7 +70,7 @@ export const AssistantGroupButton = ({
|
||||
)}
|
||||
{prompt && (
|
||||
<Radio.Button value="e" onClick={showPromptModal}>
|
||||
<SvgIcon name={`prompt`} width={16}></SvgIcon>
|
||||
<PromptIcon style={{ fontSize: '16px' }} />
|
||||
</Radio.Button>
|
||||
)}
|
||||
</Radio.Group>
|
||||
|
||||
@ -6,10 +6,6 @@
|
||||
.messageItemSectionLeft {
|
||||
width: 80%;
|
||||
}
|
||||
.messageItemSectionRight {
|
||||
// width: 80%;
|
||||
// max-width: 50vw;
|
||||
}
|
||||
.messageItemContent {
|
||||
display: inline-flex;
|
||||
gap: 20px;
|
||||
@ -36,10 +32,17 @@
|
||||
background-color: #e6f4ff;
|
||||
word-break: break-all;
|
||||
}
|
||||
.messageTextDark {
|
||||
.chunkText();
|
||||
.messageTextBase();
|
||||
background-color: #1668dc;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.messageUserText {
|
||||
.chunkText();
|
||||
.messageTextBase();
|
||||
background-color: rgb(248, 247, 247);
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
word-break: break-all;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
|
||||
import FileIcon from '../file-icon';
|
||||
import IndentedTreeModal from '../indented-tree/modal';
|
||||
import NewDocumentLink from '../new-document-link';
|
||||
import { useTheme } from '../theme-provider';
|
||||
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
||||
import styles from './index.less';
|
||||
|
||||
@ -47,6 +48,7 @@ const MessageItem = ({
|
||||
regenerateMessage,
|
||||
showLikeButton = true,
|
||||
}: IProps) => {
|
||||
const { theme } = useTheme();
|
||||
const isAssistant = item.role === MessageType.Assistant;
|
||||
const isUser = item.role === MessageType.User;
|
||||
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
|
||||
@ -139,7 +141,11 @@ const MessageItem = ({
|
||||
</Space>
|
||||
<div
|
||||
className={
|
||||
isAssistant ? styles.messageText : styles.messageUserText
|
||||
isAssistant
|
||||
? theme === 'dark'
|
||||
? styles.messageTextDark
|
||||
: styles.messageText
|
||||
: styles.messageUserText
|
||||
}
|
||||
>
|
||||
<MarkdownContent
|
||||
@ -181,8 +187,8 @@ const MessageItem = ({
|
||||
dataSource={documentList}
|
||||
renderItem={(item) => {
|
||||
// TODO:
|
||||
const fileThumbnail =
|
||||
documentThumbnails[item.id] || documentThumbnails[item.id];
|
||||
// const fileThumbnail =
|
||||
// documentThumbnails[item.id] || documentThumbnails[item.id];
|
||||
const fileExtension = getExtension(item.name);
|
||||
return (
|
||||
<List.Item>
|
||||
|
||||
@ -35,7 +35,7 @@ const RetrievalDocuments = ({
|
||||
>
|
||||
<Space>
|
||||
<span>
|
||||
{selectedDocumentIds.length ?? 0}/{documents.length}
|
||||
{selectedDocumentIds?.length ?? 0}/{documents?.length ?? 0}
|
||||
</span>
|
||||
{t('knowledgeDetails.filesSelected')}
|
||||
</Space>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
type Theme = 'dark' | 'light' | 'system';
|
||||
type Theme = 'dark' | 'light';
|
||||
|
||||
type ThemeProviderProps = {
|
||||
children: React.ReactNode;
|
||||
@ -14,7 +14,7 @@ type ThemeProviderState = {
|
||||
};
|
||||
|
||||
const initialState: ThemeProviderState = {
|
||||
theme: 'system',
|
||||
theme: 'light',
|
||||
setTheme: () => null,
|
||||
};
|
||||
|
||||
@ -22,7 +22,7 @@ const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
defaultTheme = 'system',
|
||||
defaultTheme = 'light',
|
||||
storageKey = 'vite-ui-theme',
|
||||
...props
|
||||
}: ThemeProviderProps) {
|
||||
@ -32,32 +32,19 @@ export function ThemeProvider({
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
|
||||
root.classList.remove('light', 'dark');
|
||||
|
||||
if (theme === 'system') {
|
||||
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
.matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
|
||||
root.classList.add(systemTheme);
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(storageKey, theme);
|
||||
root.classList.add(theme);
|
||||
}, [theme]);
|
||||
|
||||
const value = {
|
||||
theme,
|
||||
setTheme: (theme: Theme) => {
|
||||
localStorage.setItem(storageKey, theme);
|
||||
setTheme(theme);
|
||||
},
|
||||
};
|
||||
}, [storageKey, theme]);
|
||||
|
||||
return (
|
||||
<ThemeProviderContext.Provider {...props} value={value}>
|
||||
<ThemeProviderContext.Provider
|
||||
{...props}
|
||||
value={{
|
||||
theme,
|
||||
setTheme,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ThemeProviderContext.Provider>
|
||||
);
|
||||
|
||||
@ -123,6 +123,6 @@ export {
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenuViewport,
|
||||
navigationMenuTriggerStyle,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user