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:
so95
2024-12-10 10:42:04 +07:00
committed by GitHub
parent 7d4f1c0645
commit d5a322a352
85 changed files with 1041 additions and 520 deletions

View File

@ -16,4 +16,5 @@
.apiLinkText {
.linkText();
margin: 0 !important;
background-color: rgba(255, 255, 255, 0.1);
}

View File

@ -4,5 +4,5 @@
.codeText {
padding: 10px;
background-color: #e8e8ea;
background-color: #ffffff09;
}

View File

@ -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>
);

View File

@ -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);
}

View File

@ -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);

View File

@ -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>

View File

@ -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;

View File

@ -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>
)}

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -35,7 +35,7 @@ const RetrievalDocuments = ({
>
<Space>
<span>
{selectedDocumentIds.length ?? 0}/{documents.length}
{selectedDocumentIds?.length ?? 0}/{documents?.length ?? 0}
</span>
{t('knowledgeDetails.filesSelected')}
</Space>

View File

@ -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>
);

View File

@ -123,6 +123,6 @@ export {
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
NavigationMenuViewport,
navigationMenuTriggerStyle,
};