@lobehub/chat 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.changelogrc.js +1 -0
  2. package/.commitlintrc.js +1 -0
  3. package/.editorconfig +16 -0
  4. package/.eslintignore +32 -0
  5. package/.eslintrc.js +6 -0
  6. package/.github/ISSUE_TEMPLATE/1_bug_report.yml +45 -0
  7. package/.github/ISSUE_TEMPLATE/2_feature_request.yml +21 -0
  8. package/.github/ISSUE_TEMPLATE/3_question.yml +15 -0
  9. package/.github/ISSUE_TEMPLATE/4_other.md +7 -0
  10. package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  11. package/.github/dependabot.yml +17 -0
  12. package/.github/workflows/auto-merge.yml +32 -0
  13. package/.github/workflows/contributor-help.yml +29 -0
  14. package/.github/workflows/issue-check-inactive.yml +22 -0
  15. package/.github/workflows/issue-close-require.yml +46 -0
  16. package/.github/workflows/issue-remove-inactive.yml +25 -0
  17. package/.github/workflows/release.yml +34 -0
  18. package/.github/workflows/test.yml +30 -0
  19. package/.gitpod.yml +3 -0
  20. package/.husky/commit-msg +4 -0
  21. package/.husky/pre-commit +5 -0
  22. package/.i18nrc.js +13 -0
  23. package/.prettierignore +63 -0
  24. package/.prettierrc.js +1 -0
  25. package/.releaserc.js +1 -0
  26. package/.remarkrc.js +1 -0
  27. package/.stylelintrc.js +8 -0
  28. package/CHANGELOG.md +80 -0
  29. package/README.md +147 -0
  30. package/locales/en_US/common.json +40 -0
  31. package/locales/en_US/setting.json +97 -0
  32. package/locales/zh_CN/common.json +40 -0
  33. package/locales/zh_CN/setting.json +98 -0
  34. package/next.config.mjs +32 -0
  35. package/package.json +138 -0
  36. package/public/next.svg +1 -0
  37. package/public/vercel.svg +1 -0
  38. package/scripts/genDefaultLocale.mjs +12 -0
  39. package/scripts/toc.mjs +40 -0
  40. package/src/const/fetch.ts +1 -0
  41. package/src/const/modelTokens.ts +8 -0
  42. package/src/features/FolderPanel/index.tsx +55 -0
  43. package/src/helpers/prompt.test.ts +36 -0
  44. package/src/helpers/prompt.ts +36 -0
  45. package/src/helpers/url.ts +17 -0
  46. package/src/layout/index.tsx +42 -0
  47. package/src/layout/style.ts +18 -0
  48. package/src/locales/create.ts +48 -0
  49. package/src/locales/default/common.ts +41 -0
  50. package/src/locales/default/setting.ts +97 -0
  51. package/src/locales/index.ts +5 -0
  52. package/src/locales/resources/en_US.ts +9 -0
  53. package/src/locales/resources/index.ts +7 -0
  54. package/src/locales/resources/zh_CN.ts +9 -0
  55. package/src/migrations/FromV0ToV1.ts +12 -0
  56. package/src/migrations/index.ts +13 -0
  57. package/src/pages/Sidebar.tsx +36 -0
  58. package/src/pages/_app.page.tsx +13 -0
  59. package/src/pages/_document.page.tsx +70 -0
  60. package/src/pages/api/LangChainStream.ts +95 -0
  61. package/src/pages/api/chain.api.ts +17 -0
  62. package/src/pages/api/openai.api.ts +31 -0
  63. package/src/pages/chat/SessionList/Header.tsx +56 -0
  64. package/src/pages/chat/SessionList/List/SessionItem.tsx +90 -0
  65. package/src/pages/chat/SessionList/List/index.tsx +31 -0
  66. package/src/pages/chat/SessionList/List/style.ts +77 -0
  67. package/src/pages/chat/SessionList/index.tsx +18 -0
  68. package/src/pages/chat/[id]/Config/ConfigCell.tsx +68 -0
  69. package/src/pages/chat/[id]/Config/ReadMode.tsx +63 -0
  70. package/src/pages/chat/[id]/Config/index.tsx +79 -0
  71. package/src/pages/chat/[id]/Conversation/ChatList.tsx +36 -0
  72. package/src/pages/chat/[id]/Conversation/Input.tsx +61 -0
  73. package/src/pages/chat/[id]/Conversation/index.tsx +32 -0
  74. package/src/pages/chat/[id]/Header.tsx +86 -0
  75. package/src/pages/chat/[id]/edit/AgentConfig.tsx +95 -0
  76. package/src/pages/chat/[id]/edit/AgentMeta.tsx +117 -0
  77. package/src/pages/chat/[id]/edit/FormItem.tsx +26 -0
  78. package/src/pages/chat/[id]/edit/Prompt.tsx +68 -0
  79. package/src/pages/chat/[id]/edit/index.page.tsx +62 -0
  80. package/src/pages/chat/[id]/edit/style.ts +42 -0
  81. package/src/pages/chat/[id]/index.page.tsx +40 -0
  82. package/src/pages/chat/index.page.tsx +1 -0
  83. package/src/pages/chat/layout.tsx +51 -0
  84. package/src/pages/index.page.tsx +1 -0
  85. package/src/pages/setting/Header.tsx +27 -0
  86. package/src/pages/setting/SettingForm.tsx +42 -0
  87. package/src/pages/setting/index.page.tsx +41 -0
  88. package/src/prompts/agent.ts +65 -0
  89. package/src/services/chatModel.ts +34 -0
  90. package/src/services/langChain.ts +18 -0
  91. package/src/services/url.ts +8 -0
  92. package/src/store/middleware/createHashStorage.ts +49 -0
  93. package/src/store/session/index.ts +33 -0
  94. package/src/store/session/initialState.ts +11 -0
  95. package/src/store/session/selectors.ts +3 -0
  96. package/src/store/session/slices/agentConfig/action.ts +226 -0
  97. package/src/store/session/slices/agentConfig/index.ts +3 -0
  98. package/src/store/session/slices/agentConfig/initialState.ts +34 -0
  99. package/src/store/session/slices/agentConfig/selectors.ts +54 -0
  100. package/src/store/session/slices/chat/action.ts +210 -0
  101. package/src/store/session/slices/chat/index.ts +3 -0
  102. package/src/store/session/slices/chat/initialState.ts +12 -0
  103. package/src/store/session/slices/chat/messageReducer.test.ts +70 -0
  104. package/src/store/session/slices/chat/messageReducer.ts +84 -0
  105. package/src/store/session/slices/chat/selectors.ts +83 -0
  106. package/src/store/session/slices/session/action.ts +118 -0
  107. package/src/store/session/slices/session/index.ts +3 -0
  108. package/src/store/session/slices/session/initialState.ts +31 -0
  109. package/src/store/session/slices/session/reducers/session.test.ts +456 -0
  110. package/src/store/session/slices/session/reducers/session.ts +113 -0
  111. package/src/store/session/slices/session/selectors/chat.ts +4 -0
  112. package/src/store/session/slices/session/selectors/index.ts +20 -0
  113. package/src/store/session/slices/session/selectors/list.ts +65 -0
  114. package/src/store/session/store.ts +17 -0
  115. package/src/store/settings/action.ts +31 -0
  116. package/src/store/settings/index.ts +23 -0
  117. package/src/store/settings/initialState.ts +25 -0
  118. package/src/store/settings/selectors.ts +9 -0
  119. package/src/store/settings/store.ts +13 -0
  120. package/src/styles/antdOverride.ts +29 -0
  121. package/src/styles/global.ts +23 -0
  122. package/src/styles/index.ts +6 -0
  123. package/src/types/chatMessage.ts +46 -0
  124. package/src/types/exportConfig.ts +23 -0
  125. package/src/types/global.d.ts +14 -0
  126. package/src/types/i18next.d.ts +8 -0
  127. package/src/types/langchain.ts +34 -0
  128. package/src/types/llm.ts +49 -0
  129. package/src/types/locale.ts +7 -0
  130. package/src/types/meta.ts +26 -0
  131. package/src/types/openai.ts +62 -0
  132. package/src/types/session.ts +59 -0
  133. package/src/utils/VersionController.test.ts +90 -0
  134. package/src/utils/VersionController.ts +64 -0
  135. package/src/utils/compass.ts +94 -0
  136. package/src/utils/fetch.ts +132 -0
  137. package/src/utils/filter.test.ts +120 -0
  138. package/src/utils/filter.ts +29 -0
  139. package/src/utils/uploadFIle.ts +8 -0
  140. package/src/utils/uuid.ts +9 -0
  141. package/tsconfig.json +26 -0
  142. package/vitest.config.ts +11 -0
@@ -0,0 +1,86 @@
1
+ import { ActionIcon, Avatar, ChatHeader } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { ArchiveIcon, LucideEdit, MoreVerticalIcon, Share2Icon } from 'lucide-react';
4
+ import Router from 'next/router';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+ import { shallow } from 'zustand/shallow';
9
+
10
+ import { sessionSelectors, useSessionStore } from '@/store/session';
11
+
12
+ const useStyles = createStyles(({ css, token }) => ({
13
+ desc: css`
14
+ font-size: 12px;
15
+ color: ${token.colorTextTertiary};
16
+ `,
17
+ title: css`
18
+ font-weight: bold;
19
+ color: ${token.colorText};
20
+ `,
21
+ }));
22
+ const Header = memo(() => {
23
+ const { t } = useTranslation('common');
24
+ const [meta, id] = useSessionStore((s) => {
25
+ const chat = sessionSelectors.currentSession(s);
26
+ return [chat?.meta, s.activeId];
27
+ }, shallow);
28
+
29
+ const [
30
+ // genShareUrl,
31
+
32
+ toggleConfig,
33
+ ] = useSessionStore(
34
+ (s) => [
35
+ // s.genShareUrl,
36
+ s.toggleConfig,
37
+ ],
38
+ shallow,
39
+ );
40
+
41
+ const { styles } = useStyles();
42
+ return (
43
+ <ChatHeader
44
+ left={
45
+ <>
46
+ <Avatar avatar={meta && sessionSelectors.getAgentAvatar(meta)} size={40} title={'123'} />
47
+ <Flexbox>
48
+ <Flexbox className={styles.title}>{meta?.title || t('defaultAgent')}</Flexbox>
49
+ <Flexbox className={styles.desc}>{meta?.description || t('noDescription')}</Flexbox>
50
+ </Flexbox>
51
+ </>
52
+ }
53
+ right={
54
+ id && (
55
+ <>
56
+ <ActionIcon
57
+ icon={Share2Icon}
58
+ onClick={() => {
59
+ // genShareUrl();
60
+ }}
61
+ size={{ fontSize: 24 }}
62
+ title={t('share')}
63
+ />
64
+ <ActionIcon icon={ArchiveIcon} size={{ fontSize: 24 }} title={t('archive')} />
65
+ <ActionIcon
66
+ icon={LucideEdit}
67
+ onClick={() => {
68
+ Router.push(`/chat/${id}/edit`);
69
+ }}
70
+ size={{ blockSize: 32, fontSize: 20 }}
71
+ title={t('edit')}
72
+ />
73
+ <ActionIcon
74
+ icon={MoreVerticalIcon}
75
+ onClick={() => toggleConfig()}
76
+ size={{ fontSize: 24 }}
77
+ title={t('sessionSetting')}
78
+ />
79
+ </>
80
+ )
81
+ }
82
+ />
83
+ );
84
+ });
85
+
86
+ export default Header;
@@ -0,0 +1,95 @@
1
+ import { Collapse, InputNumber, Segmented, Slider } from 'antd';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+ import { shallow } from 'zustand/shallow';
6
+
7
+ import { agentSelectors, useSessionStore } from '@/store/session';
8
+ import { LanguageModel } from '@/types/llm';
9
+
10
+ import { FormItem } from './FormItem';
11
+ import Prompt from './Prompt';
12
+ import { useStyles } from './style';
13
+
14
+ const AgentConfig = () => {
15
+ const { t } = useTranslation('common');
16
+
17
+ const { styles, theme } = useStyles();
18
+
19
+ const config = useSessionStore(agentSelectors.currentAgentConfigSafe, isEqual);
20
+
21
+ const [updateAgentConfig] = useSessionStore((s) => [s.updateAgentConfig], shallow);
22
+
23
+ return (
24
+ <>
25
+ <Flexbox
26
+ align={'center'}
27
+ distribution={'space-between'}
28
+ horizontal
29
+ paddingBlock={12}
30
+ style={{
31
+ borderBottom: `1px solid ${theme.colorBorder}`,
32
+ }}
33
+ >
34
+ <Flexbox className={styles.profile}> {t('modelConfig')}</Flexbox>
35
+ </Flexbox>
36
+ <Flexbox gap={24}>
37
+ <FormItem label={t('agentModel')}>
38
+ <Segmented
39
+ block
40
+ onChange={(value) => {
41
+ updateAgentConfig({ model: value as LanguageModel });
42
+ }}
43
+ options={Object.values(LanguageModel).map((value) => ({
44
+ label: t(value),
45
+ value,
46
+ }))}
47
+ size={'large'}
48
+ value={config.model}
49
+ />
50
+ </FormItem>
51
+ <Prompt />
52
+ <Collapse
53
+ activeKey={['advanceSettings']}
54
+ bordered={false}
55
+ className={styles.title}
56
+ expandIconPosition={'end'}
57
+ items={[
58
+ {
59
+ children: (
60
+ <Flexbox paddingBlock={16}>
61
+ <FormItem label={t('modelTemperature')}>
62
+ <Flexbox gap={16} horizontal>
63
+ <Slider
64
+ max={1}
65
+ min={0}
66
+ onChange={(value) => {
67
+ updateAgentConfig({ params: { temperature: value } });
68
+ }}
69
+ step={0.1}
70
+ style={{ flex: 1 }}
71
+ value={Number(config.params.temperature)}
72
+ />
73
+ <InputNumber
74
+ max={1}
75
+ min={0}
76
+ onChange={(value) => {
77
+ if (value) updateAgentConfig({ params: { temperature: value } });
78
+ }}
79
+ value={config.params.temperature}
80
+ />
81
+ </Flexbox>
82
+ </FormItem>
83
+ </Flexbox>
84
+ ),
85
+ key: 'advanceSettings',
86
+ label: t('advanceSettings'),
87
+ },
88
+ ]}
89
+ />
90
+ </Flexbox>
91
+ </>
92
+ );
93
+ };
94
+
95
+ export default AgentConfig;
@@ -0,0 +1,117 @@
1
+ import { ActionIcon, Avatar, Input, Tooltip } from '@lobehub/ui';
2
+ import { Button, Collapse } from 'antd';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { LucideSparkles } from 'lucide-react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+ import { shallow } from 'zustand/shallow';
8
+
9
+ import { agentSelectors, useSessionStore } from '@/store/session';
10
+
11
+ import { FormItem } from './FormItem';
12
+ import { useStyles } from './style';
13
+
14
+ const AgentMeta = () => {
15
+ const { t } = useTranslation('common');
16
+
17
+ const { styles, theme } = useStyles();
18
+
19
+ const metaData = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
20
+
21
+ const [
22
+ autocompleteMeta,
23
+ autocompleteSessionAgentMeta,
24
+ loading,
25
+ updateAgentMeta,
26
+ id,
27
+ hasSystemRole,
28
+ ] = useSessionStore(
29
+ (s) => [
30
+ s.autocompleteMeta,
31
+ s.autocompleteSessionAgentMeta,
32
+ s.autocompleteLoading,
33
+ s.updateAgentMeta,
34
+ s.activeId,
35
+ agentSelectors.hasSystemRole(s),
36
+ ],
37
+ shallow,
38
+ );
39
+
40
+ const basic = [
41
+ { key: 'title', label: t('agentName'), placeholder: t('agentNamePlaceholder') },
42
+ {
43
+ key: 'description',
44
+ label: t('agentDescription'),
45
+ placeholder: t('agentDescriptionPlaceholder'),
46
+ },
47
+ // { key: 'tag', label: t('agentTag'), placeholder: t('agentTagPlaceholder') },
48
+ ];
49
+
50
+ return (
51
+ <Collapse
52
+ defaultActiveKey={hasSystemRole ? ['meta'] : []}
53
+ items={[
54
+ {
55
+ children: (
56
+ <Flexbox gap={80} horizontal style={{ marginTop: 16 }}>
57
+ <Flexbox flex={1} gap={24}>
58
+ {basic.map((item) => (
59
+ <FormItem key={item.key} label={item.label}>
60
+ <Input
61
+ onChange={(e) => {
62
+ updateAgentMeta({ [item.key]: e.target.value });
63
+ }}
64
+ placeholder={item.placeholder}
65
+ suffix={
66
+ <ActionIcon
67
+ icon={LucideSparkles}
68
+ loading={loading[item.key as keyof typeof loading]}
69
+ onClick={() => {
70
+ autocompleteMeta(item.key as keyof typeof metaData);
71
+ }}
72
+ size={'small'}
73
+ style={{
74
+ color: theme.purple,
75
+ }}
76
+ title={t('autoGenerate')}
77
+ />
78
+ }
79
+ type={'block'}
80
+ value={metaData[item.key as keyof typeof metaData]}
81
+ />
82
+ </FormItem>
83
+ ))}
84
+ </Flexbox>
85
+ <FormItem label={t('agentAvatar')}>
86
+ <Avatar avatar={metaData.avatar} size={200} />
87
+ </FormItem>
88
+ </Flexbox>
89
+ ),
90
+ className: styles.collapseHeader,
91
+ extra: (
92
+ <Tooltip title={t('autoGenerateTooltip')}>
93
+ <Button
94
+ disabled={!hasSystemRole}
95
+ loading={Object.values(loading).some((i) => !!i)}
96
+ onClick={(e) => {
97
+ e.stopPropagation();
98
+ console.log(id);
99
+ if (!id) return;
100
+
101
+ autocompleteSessionAgentMeta(id, true);
102
+ }}
103
+ size={'large'}
104
+ >
105
+ {t('autoGenerate')}
106
+ </Button>
107
+ </Tooltip>
108
+ ),
109
+ key: 'meta',
110
+ label: <Flexbox className={styles.profile}>{t('profile')}</Flexbox>,
111
+ },
112
+ ]}
113
+ />
114
+ );
115
+ };
116
+
117
+ export default AgentMeta;
@@ -0,0 +1,26 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { ReactNode, memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ const useStyles = createStyles(({ css, token }) => ({
6
+ header: css``,
7
+ title: css`
8
+ color: ${token.colorTextSecondary};
9
+ `,
10
+ }));
11
+
12
+ interface FormItemProps {
13
+ children: ReactNode;
14
+ label: string;
15
+ }
16
+
17
+ export const FormItem = memo<FormItemProps>(({ label, children }) => {
18
+ const { styles } = useStyles();
19
+
20
+ return (
21
+ <Flexbox className={styles.header} gap={12}>
22
+ <Flexbox className={styles.title}>{label}</Flexbox>
23
+ <Flexbox>{children}</Flexbox>
24
+ </Flexbox>
25
+ );
26
+ });
@@ -0,0 +1,68 @@
1
+ import { EditableMessage } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+ import { shallow } from 'zustand/shallow';
8
+
9
+ import { agentSelectors, useSessionStore } from '@/store/session';
10
+
11
+ import { FormItem } from './FormItem';
12
+
13
+ export const useStyles = createStyles(({ css, token }) => ({
14
+ input: css`
15
+ padding: 12px;
16
+ background: ${token.colorFillTertiary};
17
+ border: 1px solid ${token.colorPrimaryBorder};
18
+ border-radius: 8px;
19
+ `,
20
+ markdown: css`
21
+ padding: 12px;
22
+ background: ${token.colorFillTertiary};
23
+ `,
24
+ }));
25
+
26
+ const Prompt = () => {
27
+ const { t } = useTranslation('common');
28
+
29
+ const [editing, setEditing] = useState(false);
30
+ const { styles } = useStyles();
31
+
32
+ const systemRole = useSessionStore((s) => {
33
+ const config = agentSelectors.currentAgentConfigSafe(s);
34
+ return config.systemRole;
35
+ }, shallow);
36
+
37
+ const [updateAgentConfig] = useSessionStore((s) => [s.updateAgentConfig], shallow);
38
+
39
+ return (
40
+ <FormItem label={t('agentPrompt')}>
41
+ <Flexbox gap={16}>
42
+ <EditableMessage
43
+ classNames={styles}
44
+ editing={editing}
45
+ onChange={(e) => {
46
+ updateAgentConfig({ systemRole: e });
47
+ }}
48
+ onEditingChange={setEditing}
49
+ value={systemRole}
50
+ />
51
+ {!editing && (
52
+ <Flexbox direction={'horizontal-reverse'}>
53
+ <Button
54
+ onClick={() => {
55
+ setEditing(true);
56
+ }}
57
+ type={'primary'}
58
+ >
59
+ {t('edit')}
60
+ </Button>
61
+ </Flexbox>
62
+ )}
63
+ </Flexbox>
64
+ </FormItem>
65
+ );
66
+ };
67
+
68
+ export default Prompt;
@@ -0,0 +1,62 @@
1
+ import { ChatHeader } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import Router from 'next/router';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import ChatLayout from '../../layout';
10
+ import AgentConfig from './AgentConfig';
11
+ import AgentMeta from './AgentMeta';
12
+
13
+ const useStyles = createStyles(({ css, token }) => ({
14
+ footer: css`
15
+ position: sticky;
16
+ bottom: 0;
17
+ border-top: 1px solid ${token.colorBorder};
18
+ `,
19
+ form: css`
20
+ overflow-y: auto;
21
+ `,
22
+ header: css`
23
+ background: ${token.colorBgContainer};
24
+ border-bottom: 1px solid ${token.colorSplit};
25
+ `,
26
+ title: css`
27
+ font-size: 16px;
28
+ font-weight: 500;
29
+ `,
30
+ }));
31
+
32
+ const EditPage = memo(() => {
33
+ const { t } = useTranslation('common');
34
+
35
+ const { styles } = useStyles();
36
+
37
+ return (
38
+ <ChatLayout>
39
+ <Flexbox height={'100vh'} style={{ position: 'relative' }} width={'100%'}>
40
+ {/*header*/}
41
+ <ChatHeader
42
+ left={<div className={styles.title}>{t('editAgentProfile')}</div>}
43
+ onBackClick={() => Router.back()}
44
+ right={
45
+ <>
46
+ <Button>{t('share')}</Button>
47
+ <Button type={'primary'}>{t('export')}</Button>
48
+ </>
49
+ }
50
+ showBackButton
51
+ />
52
+ {/*form*/}
53
+ <Flexbox className={styles.form} flex={1} gap={10} padding={24}>
54
+ <AgentMeta />
55
+ <AgentConfig />
56
+ </Flexbox>
57
+ </Flexbox>
58
+ </ChatLayout>
59
+ );
60
+ });
61
+
62
+ export default EditPage;
@@ -0,0 +1,42 @@
1
+ import { createStyles } from 'antd-style';
2
+
3
+ export const useStyles = createStyles(({ css, token }) => ({
4
+ collapseHeader: css`
5
+ .ant-collapse-header {
6
+ align-items: center !important;
7
+ }
8
+ `,
9
+ footer: css`
10
+ position: sticky;
11
+ bottom: 0;
12
+ border-top: 1px solid ${token.colorBorder};
13
+ `,
14
+ form: css`
15
+ overflow-y: auto;
16
+ `,
17
+ header: css`
18
+ background: ${token.colorBgContainer};
19
+ border-bottom: 1px solid ${token.colorSplit};
20
+ `,
21
+ profile: css`
22
+ font-size: 20px;
23
+ font-weight: bold;
24
+ color: ${token.colorText};
25
+ `,
26
+ prompt: css`
27
+ padding: 12px;
28
+ background: ${token.colorFillTertiary};
29
+ `,
30
+ title: css`
31
+ font-size: 16px;
32
+ font-weight: 500;
33
+
34
+ .ant-collapse-header {
35
+ padding: 0 !important;
36
+ }
37
+
38
+ .ant-collapse-content-box {
39
+ padding: 0 !important;
40
+ }
41
+ `,
42
+ }));
@@ -0,0 +1,40 @@
1
+ import isEqual from 'fast-deep-equal';
2
+ import Head from 'next/head';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { sessionSelectors, useSessionStore } from '@/store/session';
7
+
8
+ import Layout from '../layout';
9
+ import Config from './Config';
10
+ import Conversation from './Conversation';
11
+ import Header from './Header';
12
+
13
+ const Chat = memo(() => {
14
+ const [title] = useSessionStore((s) => {
15
+ const context = sessionSelectors.currentSession(s);
16
+ return [context?.meta.title];
17
+ }, isEqual);
18
+
19
+ const pageTitle = title ? `${title} - LobeChat` : 'LobeChat';
20
+
21
+ return (
22
+ <Layout>
23
+ <Head>
24
+ <title>{pageTitle}</title>
25
+ </Head>
26
+
27
+ <Flexbox flex={1}>
28
+ <Header />
29
+ <Flexbox
30
+ id={'lobe-conversion-container'}
31
+ style={{ height: 'calc(100vh - 64px)', position: 'relative' }}
32
+ >
33
+ <Conversation />
34
+ <Config />
35
+ </Flexbox>
36
+ </Flexbox>
37
+ </Layout>
38
+ );
39
+ });
40
+ export default Chat;
@@ -0,0 +1 @@
1
+ export { default } from './[id]/index.page';
@@ -0,0 +1,51 @@
1
+ import { useRouter } from 'next/router';
2
+ import { PropsWithChildren, memo, useEffect } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+ import { shallow } from 'zustand/shallow';
5
+
6
+ import { createI18nNext } from '@/locales/create';
7
+ import { useSessionStore } from '@/store/session';
8
+ import { useSettings } from '@/store/settings';
9
+
10
+ import Sidebar from '../Sidebar';
11
+ import { Sessions } from './SessionList';
12
+
13
+ const initI18n = createI18nNext();
14
+
15
+ const ChatLayout = memo<PropsWithChildren>(({ children }) => {
16
+ useEffect(() => {
17
+ initI18n.finally();
18
+ }, []);
19
+
20
+ const [activeSession] = useSessionStore((s) => {
21
+ return [s.activeSession];
22
+ }, shallow);
23
+
24
+ const router = useRouter();
25
+ const { id } = router.query;
26
+
27
+ useEffect(() => {
28
+ const hasRehydrated = useSessionStore.persist.hasHydrated();
29
+ // 只有当水合完毕后,才能正常去激活会话
30
+ if (typeof id === 'string' && hasRehydrated) {
31
+ activeSession(id);
32
+ }
33
+ }, [id]);
34
+
35
+ useEffect(() => {
36
+ const hasRehydrated = useSettings.persist.hasHydrated();
37
+ if (hasRehydrated) {
38
+ useSettings.setState({ sidebarKey: 'chat' });
39
+ }
40
+ }, []);
41
+
42
+ return (
43
+ <Flexbox horizontal width={'100%'}>
44
+ <Sidebar />
45
+ <Sessions />
46
+ {children}
47
+ </Flexbox>
48
+ );
49
+ });
50
+
51
+ export default ChatLayout;
@@ -0,0 +1 @@
1
+ export { default } from './chat/index.page';
@@ -0,0 +1,27 @@
1
+ import { ChatHeader } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { useTranslation } from 'react-i18next';
4
+ import Router from 'next/router';
5
+ import { memo } from 'react';
6
+
7
+ const useStyles = createStyles(({ css, token }) => ({
8
+ title: css`
9
+ font-size: 16px;
10
+ font-weight: bold;
11
+ color: ${token.colorText};
12
+ `,
13
+ }));
14
+ const Header = memo(() => {
15
+ const { t } = useTranslation('setting');
16
+
17
+ const { styles } = useStyles();
18
+ return (
19
+ <ChatHeader
20
+ left={<div className={styles.title}>{t('header')}</div>}
21
+ onBackClick={() => Router.back()}
22
+ showBackButton
23
+ />
24
+ );
25
+ });
26
+
27
+ export default Header;
@@ -0,0 +1,42 @@
1
+ import { Form, Input, type ItemGroup } from '@lobehub/ui';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { Palette } from 'lucide-react';
4
+ import { memo, useMemo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { settingsSelectors, useSettings } from '@/store/settings';
8
+ import { ConfigKeys } from '@/types/exportConfig';
9
+
10
+ type SettingItemGroup = ItemGroup & {
11
+ children: {
12
+ name: ConfigKeys;
13
+ }[];
14
+ };
15
+
16
+ const SettingForm = memo(() => {
17
+ const settings = useSettings(settingsSelectors.currentSettings, isEqual);
18
+
19
+ const { t } = useTranslation('setting');
20
+
21
+ const theme: SettingItemGroup = useMemo(
22
+ () => ({
23
+ children: [
24
+ {
25
+ children: <Input />,
26
+ desc: t('settingTheme.avatar.desc'),
27
+ label: t('settingTheme.avatar.title'),
28
+ name: 'avatar',
29
+ },
30
+ ],
31
+ icon: Palette,
32
+ title: t('settingTheme.title'),
33
+ }),
34
+ [settings],
35
+ );
36
+
37
+ return (
38
+ <Form initialValues={settings} items={[theme]} style={{ maxWidth: 1024, width: '100%' }} />
39
+ );
40
+ });
41
+
42
+ export default SettingForm;