diff --git a/next-i18next.config.js b/next-i18next.config.js index 1135d20c9c..20fc389776 100644 --- a/next-i18next.config.js +++ b/next-i18next.config.js @@ -3,11 +3,15 @@ const i18n = require('./.i18nrc'); /** @type {import('next-i18next').UserConfig} */ module.exports = { debug: process.env.NODE_ENV === 'development', + fallbackLng: { + default: ['en'], + zh_TW: ['zh_CN'], + }, i18n: { defaultLocale: i18n.entryLocale, locales: [i18n.entryLocale, ...i18n.outputLocales], }, localePath: - typeof window === 'undefined' ? require('node:path').resolve('./', i18n.output) : '/locales', + typeof window === 'undefined' ? require('node:path').resolve('./public/locales') : '/locales', reloadOnPrerender: process.env.NODE_ENV === 'development', }; diff --git a/public/locales/en_US/common.json b/public/locales/en_US/common.json index ecd5b8bc84..2f9e4b0620 100644 --- a/public/locales/en_US/common.json +++ b/public/locales/en_US/common.json @@ -1,4 +1,16 @@ { + "agentProfile": "Agent Profile", + "archive": "Archive", + "cancel": "Cancel", + "close": "Close", + "confirmRemoveSessionItemAlert": "You are about to remove this agent. Once removed, it cannot be recovered. Please confirm your action.", + "defaultAgent": "Default Agent", + "edit": "Edit", + "newAgent": "New Agent", + "noDescription": "No description", + "ok": "OK", + "searchAgentPlaceholder": "Search agents and conversations...", + "sessionSetting": "Session Setting", "setting": "Setting", "share": "Share" } diff --git a/public/locales/ja_JP/common.json b/public/locales/ja_JP/common.json index feb14cbc24..7b1d002870 100644 --- a/public/locales/ja_JP/common.json +++ b/public/locales/ja_JP/common.json @@ -1,4 +1,16 @@ { + "agentProfile": "エージェントプロファイル", + "archive": "アーカイブ", + "cancel": "キャンセル", + "close": "閉じる", + "confirmRemoveSessionItemAlert": "このエージェントを削除します。削除後は元に戻すことはできません。操作を確認してください", + "defaultAgent": "デフォルトエージェント", + "edit": "編集", + "newAgent": "新しいエージェント", + "noDescription": "説明はありません", + "ok": "OK", + "searchAgentPlaceholder": "エージェントと会話を検索...", + "sessionSetting": "セッション設定", "setting": "設定", "share": "共有する" } diff --git a/public/locales/ko_KR/common.json b/public/locales/ko_KR/common.json index 4d2a5d96c0..8b5e8f9879 100644 --- a/public/locales/ko_KR/common.json +++ b/public/locales/ko_KR/common.json @@ -1,4 +1,16 @@ { + "agentProfile": "도우미 프로필", + "archive": "보관", + "cancel": "취소", + "close": "닫기", + "confirmRemoveSessionItemAlert": "이 도우미를 삭제하려고 합니다. 삭제 후에는 복구할 수 없으므로 작업을 확인하십시오.", + "defaultAgent": "기본 도우미", + "edit": "편집", + "newAgent": "새 도우미", + "noDescription": "설명 없음", + "ok": "확인", + "searchAgentPlaceholder": "도우미 및 대화 검색...", + "sessionSetting": "세션 설정", "setting": "설정", "share": "공유" } diff --git a/public/locales/zh_CN/common.json b/public/locales/zh_CN/common.json index 67aa02c326..eaf8eba5b3 100644 --- a/public/locales/zh_CN/common.json +++ b/public/locales/zh_CN/common.json @@ -1,4 +1,16 @@ { + "agentProfile": "助手信息", + "archive": "归档", + "cancel": "取消", + "close": "关闭", + "confirmRemoveSessionItemAlert": "即将删除该助手,删除后该将无法找回,请确认你的操作", + "defaultAgent": "默认助手", + "edit": "编辑", + "newAgent": "新建助手", + "noDescription": "暂无描述", + "ok": "确定", + "searchAgentPlaceholder": "搜索助手和对话...", + "sessionSetting": "会话设置", "setting": "设置", "share": "分享" } diff --git a/public/locales/zh_HK/common.json b/public/locales/zh_HK/common.json index 074a601894..8efb6a26cc 100644 --- a/public/locales/zh_HK/common.json +++ b/public/locales/zh_HK/common.json @@ -1,4 +1,16 @@ { + "agentProfile": "助手資訊", + "archive": "歸檔", + "cancel": "取消", + "close": "關閉", + "confirmRemoveSessionItemAlert": "即將刪除該助手,刪除後將無法找回,請確認你的操作", + "defaultAgent": "默認助手", + "edit": "編輯", + "newAgent": "新建助手", + "noDescription": "暫無描述", + "ok": "確定", + "searchAgentPlaceholder": "搜索助手和對話...", + "sessionSetting": "會話設置", "setting": "設置", "share": "分享" } diff --git a/src/features/FolderPanel/index.tsx b/src/features/FolderPanel/index.tsx index 9bd4c71811..30a3bfad12 100644 --- a/src/features/FolderPanel/index.tsx +++ b/src/features/FolderPanel/index.tsx @@ -10,6 +10,7 @@ export const useStyles = createStyles(({ css, token }) => ({ panel: css` height: 100vh; color: ${token.colorTextSecondary}; + background: ${token.colorBgContainer}; `, })); diff --git a/src/layout/index.tsx b/src/layout/index.tsx index ea0c164c81..2a7b88cf9b 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -5,7 +5,9 @@ import Zh_CN from 'antd/locale/zh_CN'; import { PropsWithChildren, useEffect } from 'react'; import { useChatStore } from 'src/store/session'; -import { GlobalStyle, useStyles } from './style'; +import { GlobalStyle } from '@/styles'; + +import { useStyles } from './style'; const Layout = ({ children }: PropsWithChildren) => { const { styles } = useStyles(); diff --git a/src/layout/style.ts b/src/layout/style.ts index b0e8dd5e9b..f9d6886f4d 100644 --- a/src/layout/style.ts +++ b/src/layout/style.ts @@ -1,7 +1,4 @@ -import { createGlobalStyle, createStyles } from 'antd-style'; -import { rgba } from 'polished'; - -export const NOTIFICATION_PRIMARY = 'notification-primary-info'; +import { createStyles } from 'antd-style'; export const useStyles = createStyles(({ css, token }) => ({ bg: css` @@ -13,82 +10,9 @@ export const useStyles = createStyles(({ css, token }) => ({ height: 100%; background: ${token.colorBgLayout}; - background-image: linear-gradient( - 180deg, - ${token.colorBgContainer} 0%, - rgba(255, 255, 255, 0%) 20% - ); :has(#ChatLayout, #FlowLayout) { overflow: hidden; } `, })); - -export const GlobalStyle = createGlobalStyle` - .ant-btn { - box-shadow: none; - } - - #__next { - height: 100%; - } - - p { - margin-bottom: 0; - } - - li { - display: block; - } - - .ant-btn-default:not(:disabled):not(.ant-btn-dangerous) { - border-color: transparent; - - &:hover { - color: ${(p) => p.theme.colorText}; - background: ${({ theme }) => (theme.isDarkMode ? theme.colorFill : theme.colorFillTertiary)}; - border-color: transparent; - } - } - - .ant-popover { - z-index: 1100; - } - - - /* 定义滚动槽的样式 */ - ::-webkit-scrollbar { - width: 6px; - height: 6px; - margin-right: 4px; - background-color: transparent; /* 定义滚动槽的背景色 */ - - &-thumb { - background-color: ${({ theme }) => theme.colorFill}; /* 定义滚动块的背景色 */ - border-radius: 4px; /* 定义滚动块的圆角半径 */ - } - - &-corner { - display: none; - } - } - - .ant-notification .ant-notification-notice.${NOTIFICATION_PRIMARY} { - background: ${(p) => p.theme.colorPrimary}; - box-shadow: 0 6px 16px 0 ${({ theme }) => rgba(theme.colorPrimary, 0.1)}, - 0 3px 6px -4px ${({ theme }) => rgba(theme.colorPrimary, 0.2)}, - 0 9px 28px 8px ${({ theme }) => rgba(theme.colorPrimary, 0.1)}; - - .anticon { - color: ${(p) => p.theme.colorTextLightSolid} - } - - .ant-notification-notice-message { - margin-bottom: 0; - padding-right: 0; - color: ${(p) => p.theme.colorTextLightSolid}; - } - } - -`; diff --git a/src/pages/chat/Config/index.tsx b/src/pages/chat/Config/index.tsx index 4ea0453876..7e54d3b1ee 100644 --- a/src/pages/chat/Config/index.tsx +++ b/src/pages/chat/Config/index.tsx @@ -1,6 +1,7 @@ -import { ActionIcon, DraggablePanel } from '@lobehub/ui'; +import { ActionIcon, DraggablePanel, DraggablePanelContainer } from '@lobehub/ui'; import { createStyles } from 'antd-style'; import { LucideEdit, LucideX } from 'lucide-react'; +import { useTranslation } from 'next-i18next'; import { Flexbox } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; @@ -8,9 +9,11 @@ import { useChatStore } from '@/store/session'; import ReadMode from './ReadMode'; +const WIDTH = 280; + const useStyles = createStyles(({ css, token }) => ({ drawer: css` - background: ${token.colorBgContainer}; + background: ${token.colorBgLayout}; `, header: css` border-bottom: 1px solid ${token.colorBorder}; @@ -18,6 +21,7 @@ const useStyles = createStyles(({ css, token }) => ({ })); const Config = () => { + const { t } = useTranslation('common'); const { styles } = useStyles(); const [showAgentSettings, toggleConfig] = useChatStore( (s) => [s.showAgentSettings, s.toggleConfig], @@ -29,32 +33,41 @@ const Config = () => { className={styles.drawer} expand={showAgentSettings} expandable={false} - minWidth={400} + maxWidth={WIDTH} + minWidth={WIDTH} mode={'float'} pin placement={'right'} resize={{ left: false }} > - - 会话设置 - - - { - toggleConfig(false); - }} - title={'关闭'} - /> + + + {t('agentProfile')} + + + { + toggleConfig(false); + }} + size={{ blockSize: 32, fontSize: 20 }} + title={t('close')} + /> + - - + + ); }; diff --git a/src/pages/chat/Header.tsx b/src/pages/chat/Header.tsx index 9b6e7d50c5..005374573b 100644 --- a/src/pages/chat/Header.tsx +++ b/src/pages/chat/Header.tsx @@ -44,8 +44,7 @@ const Header = memo(() => { align={'center'} distribution={'space-between'} horizontal - padding={8} - paddingInline={16} + padding="8px 8px 8px 16px" style={{ borderBottom: `1px solid ${theme.colorSplit}`, gridArea: 'header', @@ -54,20 +53,26 @@ const Header = memo(() => { - {meta?.title} - {meta?.description || '暂无描述'} + {meta?.title || t('defaultAgent')} + {meta?.description || t('noDescription')} - + { // genShareUrl(); }} + size={{ fontSize: 24 }} title={t('share')} /> - - + + ); diff --git a/src/pages/chat/SessionList/Header.tsx b/src/pages/chat/SessionList/Header.tsx index c610698e64..ca2dc3303b 100644 --- a/src/pages/chat/SessionList/Header.tsx +++ b/src/pages/chat/SessionList/Header.tsx @@ -1,6 +1,7 @@ import { ActionIcon, Logo, SearchBar } from '@lobehub/ui'; import { createStyles } from 'antd-style'; -import { Plus } from 'lucide-react'; +import { MessageSquarePlus } from 'lucide-react'; +import { useTranslation } from 'next-i18next'; import Link from 'next/link'; import { memo } from 'react'; import { Flexbox } from 'react-layout-kit'; @@ -20,25 +21,32 @@ export const useStyles = createStyles(({ css, token }) => ({ const Header = memo(() => { const { styles } = useStyles(); - + const { t } = useTranslation('common'); const [keywords, createSession] = useChatStore( (s) => [s.searchKeywords, s.createSession], shallow, ); return ( - + - + useChatStore.setState({ searchKeywords: e.target.value })} - placeholder="Search..." - type={'block'} + placeholder={t('searchAgentPlaceholder')} + spotlight + type={'ghost'} value={keywords} /> diff --git a/src/pages/chat/SessionList/List/SessionItem.tsx b/src/pages/chat/SessionList/List/SessionItem.tsx index 7efe9af0bf..695e240db1 100644 --- a/src/pages/chat/SessionList/List/SessionItem.tsx +++ b/src/pages/chat/SessionList/List/SessionItem.tsx @@ -1,66 +1,31 @@ -import { CloseOutlined } from '@ant-design/icons'; -import { Avatar, List } from '@lobehub/ui'; +import { ActionIcon, Avatar, List } from '@lobehub/ui'; import { Popconfirm } from 'antd'; -import { createStyles } from 'antd-style'; +import { X } from 'lucide-react'; +import { useTranslation } from 'next-i18next'; import { FC, memo } from 'react'; -import { Flexbox } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; import { sessionSelectors, useChatStore } from '@/store/session'; -const useStyles = createStyles(({ css, cx }) => { - const closeCtn = css` - position: absolute; - top: 50%; - left: 2px; - transform: translateY(-50%); - - width: 16px; - height: 16px; - - font-size: 10px; - - opacity: 0; - `; - return { - active: css` - opacity: 1; - `, - closeCtn, - container: css` - position: relative; - - &:hover { - .${cx(closeCtn)} { - opacity: 1; - } - } - `, - time: css` - align-self: flex-start; - `, - }; -}); +import { useStyles } from './style'; interface SessionItemProps { active: boolean; id: string; loading: boolean; - simple?: boolean; } -const SessionItem: FC = memo(({ id, active, simple = true, loading }) => { +const SessionItem: FC = memo(({ id, active = true, loading }) => { + const { t } = useTranslation('common'); const { styles, theme, cx } = useStyles(); const [title, systemRole, avatar, avatarBackground, updateAt, switchAgent, removeSession] = useChatStore((s) => { const session = sessionSelectors.getSessionById(id)(s); const meta = session.meta; - const systemRole = session.config.systemRole; - return [ - meta.title || systemRole || '默认角色', - systemRole, + meta.title || t('noDescription'), + systemRole || t('defaultAgent'), sessionSelectors.getAgentAvatar(meta), meta.backgroundColor, session?.updateAt, @@ -70,42 +35,43 @@ const SessionItem: FC = memo(({ id, active, simple = true, loa }, shallow); return ( - + + } + className={styles.container} + classNames={{ time: cx('session-time', styles.time) }} + date={updateAt} + description={title} + loading={loading} + onClick={() => { + switchAgent(id); + }} + style={{ + alignItems: 'center', + color: theme.colorText, + }} + title={systemRole} + > removeSession(id)} overlayStyle={{ width: 280 }} - placement={'right'} - title={'即将删除该会话,删除后该将无法找回,请确认你的操作。'} + title={t('confirmRemoveSessionItemAlert')} > - + - - } - classNames={{ time: styles.time }} - date={updateAt} - description={simple ? undefined : systemRole} - loading={loading} - onClick={() => { - switchAgent(id); - }} - style={{ - alignItems: 'center', - color: theme.colorText, - }} - title={title} - /> - + ); }); diff --git a/src/pages/chat/SessionList/List/index.tsx b/src/pages/chat/SessionList/List/index.tsx index 58d60c8a91..a4fd94e557 100644 --- a/src/pages/chat/SessionList/List/index.tsx +++ b/src/pages/chat/SessionList/List/index.tsx @@ -1,49 +1,30 @@ -import { createStyles } from 'antd-style'; -import { rgba } from 'polished'; import { memo } from 'react'; +import { Flexbox } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; +import { useStyles } from '@/pages/chat/SessionList/List/style'; import { sessionSelectors, useChatStore } from '@/store/session'; import SessionItem from './SessionItem'; -export const useStyles = createStyles(({ css, token }) => ({ - button: css` - position: sticky; - z-index: 30; - bottom: 0; - - display: flex; - gap: 8px; - align-items: center; - justify-content: center; - - margin-top: 8px; - padding: 12px; - - background: ${rgba(token.colorBgLayout, 0.5)}; - backdrop-filter: blur(8px); - `, -})); - const SessionList = memo(() => { + const { styles, cx } = useStyles(); const [list, activeId, loading] = useChatStore( (s) => [sessionSelectors.chatList(s), s.activeId, s.loading.summarizingTitle], shallow, ); return ( - <> + {list.map(({ id }) => ( ))} - + ); }); diff --git a/src/pages/chat/SessionList/List/style.ts b/src/pages/chat/SessionList/List/style.ts new file mode 100644 index 0000000000..0fb7b975e8 --- /dev/null +++ b/src/pages/chat/SessionList/List/style.ts @@ -0,0 +1,77 @@ +import { createStyles } from 'antd-style'; +import { rgba } from 'polished'; + +export const useStyles = createStyles(({ css, token, cx, stylish }) => { + return { + active: css` + display: flex; + `, + button: css` + position: sticky; + z-index: 30; + bottom: 0; + + display: flex; + gap: 8px; + align-items: center; + justify-content: center; + + margin-top: 8px; + padding: 12px; + + background: ${rgba(token.colorBgLayout, 0.5)}; + backdrop-filter: blur(8px); + `, + + container: css` + position: relative; + + .session-remove { + position: absolute; + top: 50%; + right: 16px; + transform: translateY(-50%); + + width: 16px; + height: 16px; + + font-size: 10px; + + opacity: 0; + background-color: ${token.colorFillTertiary}; + + transition: color 600ms ${token.motionEaseOut}, scale 400ms ${token.motionEaseOut}, + background-color 100ms ${token.motionEaseOut}, opacity 100ms ${token.motionEaseOut}; + + &:hover { + background-color: ${token.colorFill}; + } + } + + .session-time { + opacity: 1; + transition: opacity 100ms ${token.motionEaseOut}; + } + + &:hover { + .session-time { + opacity: 0; + } + + .session-remove { + opacity: 1; + } + } + `, + list: cx( + stylish.noScrollbar, + css` + overflow-x: hidden; + overflow-y: scroll; + `, + ), + time: css` + align-self: flex-start; + `, + }; +}); diff --git a/src/pages/chat/SessionList/index.tsx b/src/pages/chat/SessionList/index.tsx index f00dd9a2ad..acd8eba3eb 100644 --- a/src/pages/chat/SessionList/index.tsx +++ b/src/pages/chat/SessionList/index.tsx @@ -1,4 +1,3 @@ -import { createStyles } from 'antd-style'; import { memo } from 'react'; import { Flexbox } from 'react-layout-kit'; @@ -7,24 +6,12 @@ import FolderPanel from '@/features/FolderPanel'; import Header from './Header'; import SessionList from './List'; -export const useStyles = createStyles(({ css }) => ({ - center: css` - overflow-x: hidden; - overflow-y: scroll; - padding-inline: 4px 0; - `, -})); - export const Sessions = memo(() => { - const { styles, cx } = useStyles(); - return (
- - - + ); diff --git a/src/pages/chat/Sidebar.tsx b/src/pages/chat/Sidebar.tsx index f9f5c3a35f..b3f2bae0bc 100644 --- a/src/pages/chat/Sidebar.tsx +++ b/src/pages/chat/Sidebar.tsx @@ -1,5 +1,5 @@ import { ActionIcon, Logo, SideNav } from '@lobehub/ui'; -import { Album, MessageSquare, Settings2 } from 'lucide-react'; +import { MessageSquare, Settings2, Sticker } from 'lucide-react'; import { memo } from 'react'; import { shallow } from 'zustand/shallow'; @@ -22,7 +22,7 @@ const Sidebar = memo(() => { /> setTab('market')} size="large" /> diff --git a/src/pages/index.page.tsx b/src/pages/index.page.tsx index 1d61c552a3..ef525991d0 100644 --- a/src/pages/index.page.tsx +++ b/src/pages/index.page.tsx @@ -1,5 +1,6 @@ import type { GetStaticProps, InferGetStaticPropsType } from 'next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; +import { memo } from 'react'; import Chat from './chat/index.page'; @@ -14,4 +15,4 @@ export const getStaticProps: GetStaticProps = async ({ locale }) => ({ }, }); -export default Index; +export default memo(Index); diff --git a/src/store/session/slices/session/selectors/chat.ts b/src/store/session/slices/session/selectors/chat.ts index 85cf1c58ce..d8e5efd38d 100644 --- a/src/store/session/slices/session/selectors/chat.ts +++ b/src/store/session/slices/session/selectors/chat.ts @@ -1,4 +1,3 @@ import { MetaData } from '@/types/meta'; -export const getAgentAvatar = (s: MetaData) => - s.avatar || 'https://npm.elemecdn.com/@lobehub/assets-logo/assets/logo-3d.webp'; +export const getAgentAvatar = (s: MetaData) => s.avatar || '😎'; diff --git a/src/styles/antdOverride.ts b/src/styles/antdOverride.ts new file mode 100644 index 0000000000..b12866a9eb --- /dev/null +++ b/src/styles/antdOverride.ts @@ -0,0 +1,39 @@ +import { Theme, css } from 'antd-style'; +import { rgba } from 'polished'; + +export default (token: Theme) => css` + .ant-btn { + box-shadow: none; + } + + .ant-btn-default:not(:disabled):not(.ant-btn-dangerous) { + border-color: transparent; + + &:hover { + color: ${token.colorText}; + background: ${token.isDarkMode ? token.colorFill : token.colorFillTertiary}; + border-color: transparent; + } + } + + .ant-popover { + z-index: 1100; + } + + .ant-notification .ant-notification-notice.notification-primary-info { + background: ${token.colorPrimary}; + box-shadow: 0 6px 16px 0 ${rgba(token.colorPrimary, 0.1)}, + 0 3px 6px -4px ${rgba(token.colorPrimary, 0.2)}, + 0 9px 28px 8px ${rgba(token.colorPrimary, 0.1)}; + + .anticon { + color: ${token.colorTextLightSolid}; + } + + .ant-notification-notice-message { + margin-bottom: 0; + padding-right: 0; + color: ${token.colorTextLightSolid}; + } + } +`; diff --git a/src/styles/global.ts b/src/styles/global.ts new file mode 100644 index 0000000000..1d8c4fee3c --- /dev/null +++ b/src/styles/global.ts @@ -0,0 +1,23 @@ +import { css } from 'antd-style'; + +export default () => css` + body, + .ant-app { + ::-webkit-scrollbar { + width: 0; + height: 0; + } + } + + #__next { + height: 100%; + } + + p { + margin-bottom: 0; + } + + li { + display: block; + } +`; diff --git a/src/styles/index.ts b/src/styles/index.ts new file mode 100644 index 0000000000..d4a52174ea --- /dev/null +++ b/src/styles/index.ts @@ -0,0 +1,6 @@ +import { createGlobalStyle } from 'antd-style'; + +import antdOverride from './antdOverride'; +import global from './global'; + +export const GlobalStyle = createGlobalStyle(({ theme }) => [global(), antdOverride(theme)]); diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000000..0d4762a75d --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,14 @@ +import type { LobeCustomStylish, LobeCustomToken } from '@lobehub/ui'; +import 'antd-style'; +import { AntdToken } from 'antd-style/lib/types/theme'; + +declare module 'antd-style' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface CustomToken extends LobeCustomToken {} + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface CustomStylish extends LobeCustomStylish {} +} + +declare module 'styled-components' { + export interface DefaultTheme extends AntdToken, LobeCustomToken {} +}