@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,17 @@
1
+ import { LangChainParams } from '@/types/langchain';
2
+
3
+ import { LangChainStream } from './LangChainStream';
4
+
5
+ if (!process.env.OPENAI_API_KEY) {
6
+ throw new Error('Missing env var from OpenAI');
7
+ }
8
+
9
+ export const config = {
10
+ runtime: 'edge',
11
+ };
12
+
13
+ export default async function handler(request: Request) {
14
+ const payload = (await request.json()) as LangChainParams;
15
+
16
+ return new Response(LangChainStream(payload));
17
+ }
@@ -0,0 +1,31 @@
1
+ import { OpenAIStream, StreamingTextResponse } from 'ai';
2
+ import { Configuration, OpenAIApi } from 'openai-edge';
3
+
4
+ import { OpenAIStreamPayload } from '@/types/openai';
5
+
6
+ const isDev = process.env.NODE_ENV === 'development';
7
+ const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL;
8
+
9
+ // Create an OpenAI API client (that's edge friendly!)
10
+ const config = new Configuration({
11
+ apiKey: process.env.OPENAI_API_KEY,
12
+ });
13
+
14
+ const openai = new OpenAIApi(config, isDev && OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined);
15
+
16
+ export const runtime = 'edge';
17
+
18
+ export default async function handler(req: Request) {
19
+ // Extract the `messages` from the body of the request
20
+ const { messages, ...params } = (await req.json()) as OpenAIStreamPayload;
21
+
22
+ console.log(params);
23
+ const response = await openai.createChatCompletion({
24
+ stream: true,
25
+ ...params,
26
+ messages: messages.map((m) => ({ content: m.content, role: m.role })),
27
+ });
28
+
29
+ const stream = OpenAIStream(response);
30
+ return new StreamingTextResponse(stream);
31
+ }
@@ -0,0 +1,56 @@
1
+ import { ActionIcon, Logo, SearchBar } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { MessageSquarePlus } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import Link from 'next/link';
6
+ import { memo } from 'react';
7
+ import { Flexbox } from 'react-layout-kit';
8
+ import { shallow } from 'zustand/shallow';
9
+
10
+ import { useSessionStore } from '@/store/session';
11
+
12
+ export const useStyles = createStyles(({ css, token }) => ({
13
+ logo: css`
14
+ fill: ${token.colorText};
15
+ `,
16
+ top: css`
17
+ position: sticky;
18
+ top: 0;
19
+ `,
20
+ }));
21
+
22
+ const Header = memo(() => {
23
+ const { styles } = useStyles();
24
+ const { t } = useTranslation('common');
25
+ const [keywords, createSession] = useSessionStore(
26
+ (s) => [s.searchKeywords, s.createSession],
27
+ shallow,
28
+ );
29
+
30
+ return (
31
+ <Flexbox className={styles.top} gap={16} padding={16}>
32
+ <Flexbox distribution={'space-between'} horizontal>
33
+ <Link href={'/'}>
34
+ <Logo className={styles.logo} size={36} type={'text'} />
35
+ </Link>
36
+ <ActionIcon
37
+ icon={MessageSquarePlus}
38
+ onClick={createSession}
39
+ size={{ fontSize: 24 }}
40
+ style={{ flex: 'none' }}
41
+ title={t('newAgent')}
42
+ />
43
+ </Flexbox>
44
+ <SearchBar
45
+ allowClear
46
+ onChange={(e) => useSessionStore.setState({ searchKeywords: e.target.value })}
47
+ placeholder={t('searchAgentPlaceholder')}
48
+ spotlight
49
+ type={'ghost'}
50
+ value={keywords}
51
+ />
52
+ </Flexbox>
53
+ );
54
+ });
55
+
56
+ export default Header;
@@ -0,0 +1,90 @@
1
+ import { ActionIcon, Avatar, List } from '@lobehub/ui';
2
+ import { Popconfirm } from 'antd';
3
+ import { X } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { FC, memo } from 'react';
6
+ import { Flexbox } from 'react-layout-kit';
7
+ import { shallow } from 'zustand/shallow';
8
+
9
+ import { sessionSelectors, useSessionStore } from '@/store/session';
10
+ import { DEFAULT_TITLE } from '@/store/session/slices/agentConfig';
11
+
12
+ import { useStyles } from './style';
13
+
14
+ const { Item } = List;
15
+
16
+ interface SessionItemProps {
17
+ active: boolean;
18
+ id: string;
19
+ loading: boolean;
20
+ }
21
+
22
+ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading }) => {
23
+ const { t } = useTranslation('common');
24
+ const { styles, theme, cx } = useStyles();
25
+ const [title, description, systemRole, avatar, avatarBackground, updateAt, removeSession] =
26
+ useSessionStore((s) => {
27
+ const session = sessionSelectors.getSessionById(id)(s);
28
+ const meta = session.meta;
29
+ const systemRole = session.config.systemRole;
30
+ return [
31
+ meta.title,
32
+ meta.description,
33
+ systemRole,
34
+ sessionSelectors.getAgentAvatar(meta),
35
+ meta.backgroundColor,
36
+ session?.updateAt,
37
+ s.removeSession,
38
+ ];
39
+ }, shallow);
40
+
41
+ return (
42
+ <Flexbox className={styles.container} style={{ position: 'relative' }}>
43
+ <Item
44
+ active={active}
45
+ avatar={
46
+ <Avatar
47
+ avatar={avatar}
48
+ background={avatarBackground}
49
+ shape="circle"
50
+ size={46}
51
+ title={title}
52
+ />
53
+ }
54
+ classNames={{ time: cx('session-time', styles.time) }}
55
+ date={updateAt}
56
+ description={description || systemRole}
57
+ loading={loading}
58
+ style={{
59
+ alignItems: 'center',
60
+ color: theme.colorText,
61
+ }}
62
+ title={title || DEFAULT_TITLE}
63
+ />
64
+ <Popconfirm
65
+ arrow={false}
66
+ cancelText={t('cancel')}
67
+ okButtonProps={{ danger: true }}
68
+ okText={t('ok')}
69
+ onConfirm={(e) => {
70
+ e?.stopPropagation();
71
+ removeSession(id);
72
+ }}
73
+ overlayStyle={{ width: 280 }}
74
+ title={t('confirmRemoveSessionItemAlert')}
75
+ >
76
+ <ActionIcon
77
+ className="session-remove"
78
+ icon={X}
79
+ onClick={(e) => {
80
+ e.preventDefault();
81
+ e.stopPropagation();
82
+ }}
83
+ size={'small'}
84
+ />
85
+ </Popconfirm>
86
+ </Flexbox>
87
+ );
88
+ }, shallow);
89
+
90
+ export default SessionItem;
@@ -0,0 +1,31 @@
1
+ import isEqual from 'fast-deep-equal';
2
+ import Link from 'next/link';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+ import { shallow } from 'zustand/shallow';
6
+
7
+ import { sessionSelectors, useSessionStore } from '@/store/session';
8
+
9
+ import SessionItem from './SessionItem';
10
+ import { useStyles } from './style';
11
+
12
+ const SessionList = memo(() => {
13
+ const { styles, cx } = useStyles();
14
+ const list = useSessionStore((s) => sessionSelectors.chatList(s), isEqual);
15
+ const [activeId, loading] = useSessionStore(
16
+ (s) => [s.activeId, s.autocompleteLoading.title],
17
+ shallow,
18
+ );
19
+
20
+ return (
21
+ <Flexbox className={cx(styles.list)}>
22
+ {list.map(({ id }) => (
23
+ <Link href={`/chat/${id}`} key={id}>
24
+ <SessionItem active={activeId === id} id={id} loading={loading && id === activeId} />
25
+ </Link>
26
+ ))}
27
+ </Flexbox>
28
+ );
29
+ });
30
+
31
+ export default SessionList;
@@ -0,0 +1,77 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { rgba } from 'polished';
3
+
4
+ export const useStyles = createStyles(({ css, token, cx, stylish }) => {
5
+ return {
6
+ active: css`
7
+ display: flex;
8
+ `,
9
+ button: css`
10
+ position: sticky;
11
+ z-index: 30;
12
+ bottom: 0;
13
+
14
+ display: flex;
15
+ gap: 8px;
16
+ align-items: center;
17
+ justify-content: center;
18
+
19
+ margin-top: 8px;
20
+ padding: 12px;
21
+
22
+ background: ${rgba(token.colorBgLayout, 0.5)};
23
+ backdrop-filter: blur(8px);
24
+ `,
25
+
26
+ container: css`
27
+ position: relative;
28
+
29
+ .session-remove {
30
+ position: absolute;
31
+ top: 50%;
32
+ right: 16px;
33
+ transform: translateY(-50%);
34
+
35
+ width: 16px;
36
+ height: 16px;
37
+
38
+ font-size: 10px;
39
+
40
+ opacity: 0;
41
+ background-color: ${token.colorFillTertiary};
42
+
43
+ transition: color 600ms ${token.motionEaseOut}, scale 400ms ${token.motionEaseOut},
44
+ background-color 100ms ${token.motionEaseOut}, opacity 100ms ${token.motionEaseOut};
45
+
46
+ &:hover {
47
+ background-color: ${token.colorFill};
48
+ }
49
+ }
50
+
51
+ .session-time {
52
+ opacity: 1;
53
+ transition: opacity 100ms ${token.motionEaseOut};
54
+ }
55
+
56
+ &:hover {
57
+ .session-time {
58
+ opacity: 0;
59
+ }
60
+
61
+ .session-remove {
62
+ opacity: 1;
63
+ }
64
+ }
65
+ `,
66
+ list: cx(
67
+ stylish.noScrollbar,
68
+ css`
69
+ overflow-x: hidden;
70
+ overflow-y: scroll;
71
+ `,
72
+ ),
73
+ time: css`
74
+ align-self: flex-start;
75
+ `,
76
+ };
77
+ });
@@ -0,0 +1,18 @@
1
+ import { memo } from 'react';
2
+ import { Flexbox } from 'react-layout-kit';
3
+
4
+ import FolderPanel from '@/features/FolderPanel';
5
+
6
+ import Header from './Header';
7
+ import SessionList from './List';
8
+
9
+ export const Sessions = memo(() => {
10
+ return (
11
+ <FolderPanel>
12
+ <Flexbox gap={8} height={'100%'}>
13
+ <Header />
14
+ <SessionList />
15
+ </Flexbox>
16
+ </FolderPanel>
17
+ );
18
+ });
@@ -0,0 +1,68 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { LucideChevronRight, LucideIcon } from 'lucide-react';
4
+ import { memo } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ const useStyles = createStyles(({ css, token }) => ({
8
+ container: css`
9
+ background: ${token.colorFillQuaternary};
10
+ border-radius: 6px;
11
+ `,
12
+ split: css`
13
+ border-bottom: 1px solid ${token.colorSplit};
14
+ `,
15
+ }));
16
+ export interface ConfigItem {
17
+ icon: LucideIcon;
18
+ label: string;
19
+ value?: string | number;
20
+ }
21
+
22
+ export type ConfigCellProps = ConfigItem;
23
+
24
+ export const ConfigCell = memo<ConfigCellProps>(({ icon, label, value }) => {
25
+ const { styles } = useStyles();
26
+ return (
27
+ <Flexbox
28
+ className={styles.container}
29
+ distribution={'space-between'}
30
+ horizontal
31
+ padding={'10px 12px'}
32
+ >
33
+ <Flexbox gap={8} horizontal>
34
+ <Icon icon={icon} />
35
+ <Flexbox>{label}</Flexbox>
36
+ </Flexbox>
37
+ {value ?? <Icon icon={LucideChevronRight} />}
38
+ </Flexbox>
39
+ );
40
+ });
41
+
42
+ export interface CellGroupProps {
43
+ items: ConfigItem[];
44
+ }
45
+
46
+ export const ConfigCellGroup = memo<CellGroupProps>(({ items }) => {
47
+ const { styles } = useStyles();
48
+
49
+ return (
50
+ <Flexbox className={styles.container}>
51
+ {items.map(({ label, icon, value }, index) => (
52
+ <Flexbox
53
+ className={items.length === index + 1 ? undefined : styles.split}
54
+ distribution={'space-between'}
55
+ horizontal
56
+ key={label}
57
+ padding={'10px 12px'}
58
+ >
59
+ <Flexbox gap={8} horizontal>
60
+ <Icon icon={icon} />
61
+ <Flexbox>{label}</Flexbox>
62
+ </Flexbox>
63
+ {value ?? <Icon icon={LucideChevronRight} />}
64
+ </Flexbox>
65
+ ))}
66
+ </Flexbox>
67
+ );
68
+ });
@@ -0,0 +1,63 @@
1
+ import { Avatar } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { LucideBrain, LucideThermometer, WholeWord } from 'lucide-react';
5
+ import { memo } from 'react';
6
+ import { Center, Flexbox } from 'react-layout-kit';
7
+ import { shallow } from 'zustand/shallow';
8
+
9
+ import { agentSelectors, sessionSelectors, useSessionStore } from '@/store/session';
10
+ import { DEFAULT_TITLE } from '@/store/session/slices/agentConfig';
11
+
12
+ import { ConfigCell, ConfigCellGroup } from './ConfigCell';
13
+
14
+ const useStyles = createStyles(({ css, token }) => ({
15
+ desc: css`
16
+ color: ${token.colorText};
17
+ `,
18
+ model: css`
19
+ color: ${token.colorTextTertiary};
20
+ `,
21
+ title: css`
22
+ font-size: ${token.fontSizeHeading4}px;
23
+ font-weight: bold;
24
+ `,
25
+ }));
26
+
27
+ const ReadMode = memo(() => {
28
+ const { styles } = useStyles();
29
+ const session = useSessionStore(sessionSelectors.currentSessionSafe, isEqual);
30
+ const avatar = useSessionStore(agentSelectors.currentAgentAvatar, shallow);
31
+ const title = useSessionStore(agentSelectors.currentAgentTitle, shallow);
32
+ const model = useSessionStore(agentSelectors.currentAgentModel, shallow);
33
+
34
+ return (
35
+ <Center gap={12} padding={'32px 16px'} style={{ marginTop: 8 }}>
36
+ <Avatar avatar={avatar} size={100} />
37
+ <Flexbox className={styles.title}>{title || DEFAULT_TITLE}</Flexbox>
38
+ <Flexbox className={styles.model}>{model}</Flexbox>
39
+ <Flexbox className={styles.desc}>{session.meta.description}</Flexbox>
40
+
41
+ <Flexbox flex={1} gap={12} width={'100%'}>
42
+ <ConfigCell icon={LucideBrain} label={'提示词'} />
43
+
44
+ <ConfigCellGroup
45
+ items={[
46
+ {
47
+ icon: LucideThermometer,
48
+ label: '温度',
49
+ value: session.config.params.temperature,
50
+ },
51
+ {
52
+ icon: WholeWord,
53
+ label: '会话最大长度',
54
+ value: session.config.params.max_tokens,
55
+ },
56
+ ]}
57
+ />
58
+ </Flexbox>
59
+ </Center>
60
+ );
61
+ });
62
+
63
+ export default ReadMode;
@@ -0,0 +1,79 @@
1
+ import { ActionIcon, DraggablePanel, DraggablePanelContainer } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { LucideEdit, LucideX } from 'lucide-react';
4
+ import Router from 'next/router';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+ import { shallow } from 'zustand/shallow';
8
+
9
+ import { useSessionStore } from '@/store/session';
10
+
11
+ import ReadMode from './ReadMode';
12
+
13
+ const WIDTH = 280;
14
+
15
+ const useStyles = createStyles(({ css, token }) => ({
16
+ drawer: css`
17
+ background: ${token.colorBgLayout};
18
+ `,
19
+ header: css`
20
+ border-bottom: 1px solid ${token.colorBorder};
21
+ `,
22
+ }));
23
+
24
+ const Config = () => {
25
+ const { t } = useTranslation('common');
26
+ const { styles } = useStyles();
27
+ const [showAgentSettings, toggleConfig, id] = useSessionStore(
28
+ (s) => [s.showAgentSettings, s.toggleConfig, s.activeId],
29
+ shallow,
30
+ );
31
+
32
+ return (
33
+ <DraggablePanel
34
+ className={styles.drawer}
35
+ expand={showAgentSettings}
36
+ expandable={false}
37
+ maxWidth={WIDTH}
38
+ minWidth={WIDTH}
39
+ mode={'float'}
40
+ pin
41
+ placement={'right'}
42
+ resize={{ left: false }}
43
+ >
44
+ <DraggablePanelContainer style={{ flex: 'none', minWidth: WIDTH }}>
45
+ <Flexbox
46
+ align={'center'}
47
+ className={styles.header}
48
+ distribution={'space-between'}
49
+ horizontal
50
+ padding={12}
51
+ paddingInline={16}
52
+ >
53
+ <Flexbox>{t('agentProfile')}</Flexbox>
54
+ <Flexbox gap={4} horizontal>
55
+ <ActionIcon
56
+ icon={LucideEdit}
57
+ onClick={() => {
58
+ Router.push(`/chat/${id}/edit`);
59
+ }}
60
+ size={{ blockSize: 32, fontSize: 20 }}
61
+ title={t('edit')}
62
+ />
63
+ <ActionIcon
64
+ icon={LucideX}
65
+ onClick={() => {
66
+ toggleConfig(false);
67
+ }}
68
+ size={{ blockSize: 32, fontSize: 20 }}
69
+ title={t('close')}
70
+ />
71
+ </Flexbox>
72
+ </Flexbox>
73
+ <ReadMode />
74
+ </DraggablePanelContainer>
75
+ </DraggablePanel>
76
+ );
77
+ };
78
+
79
+ export default Config;
@@ -0,0 +1,36 @@
1
+ import { ChatList } from '@lobehub/ui';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { memo } from 'react';
4
+ import { shallow } from 'zustand/shallow';
5
+
6
+ import { chatSelectors, useSessionStore } from '@/store/session';
7
+
8
+ const List = () => {
9
+ const data = useSessionStore(chatSelectors.currentChats, isEqual);
10
+ const [deleteMessage, resendMessage] = useSessionStore(
11
+ (s) => [s.deleteMessage, s.resendMessage],
12
+ shallow,
13
+ );
14
+
15
+ return (
16
+ <ChatList
17
+ data={data}
18
+ onActionClick={(key, id) => {
19
+ switch (key) {
20
+ case 'delete': {
21
+ deleteMessage(id);
22
+ break;
23
+ }
24
+
25
+ case 'regenerate': {
26
+ resendMessage(id);
27
+ break;
28
+ }
29
+ }
30
+ }}
31
+ style={{ marginTop: 24 }}
32
+ />
33
+ );
34
+ };
35
+
36
+ export default memo(List);
@@ -0,0 +1,61 @@
1
+ import { ActionIcon, ChatInputArea, DraggablePanel, Icon, TokenTag } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { encode } from 'gpt-tokenizer';
4
+ import { Archive, Eraser, Languages } from 'lucide-react';
5
+ import { memo, useMemo, useState } from 'react';
6
+ import { shallow } from 'zustand/shallow';
7
+
8
+ import { ModelTokens } from '@/const/modelTokens';
9
+ import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session';
10
+ import { useSettings } from '@/store/settings';
11
+
12
+ const ChatInput = () => {
13
+ const [expand, setExpand] = useState<boolean>(false);
14
+ const [text, setText] = useState('');
15
+ const textTokenCount = useMemo(() => encode(text).length, [text]);
16
+
17
+ const [inputHeight] = useSettings((s) => [s.inputHeight], shallow);
18
+ const [totalToken, model, sendMessage, clearMessage] = useSessionStore(
19
+ (s) => [
20
+ chatSelectors.totalTokenCount(s),
21
+ agentSelectors.currentAgentModel(s),
22
+ s.createOrSendMsg,
23
+ s.clearMessage,
24
+ ],
25
+ shallow,
26
+ );
27
+
28
+ return (
29
+ <DraggablePanel
30
+ expandable={false}
31
+ fullscreen={expand}
32
+ minHeight={200}
33
+ onSizeChange={(_, size) => {
34
+ if (!size) return;
35
+ useSettings.setState({
36
+ inputHeight: typeof size.height === 'string' ? Number.parseInt(size.height) : size.height,
37
+ });
38
+ }}
39
+ placement="bottom"
40
+ size={{ height: inputHeight, width: '100%' }}
41
+ >
42
+ <ChatInputArea
43
+ actions={
44
+ <>
45
+ <ActionIcon icon={Languages} />
46
+ <ActionIcon icon={Eraser} onClick={clearMessage} />
47
+ <TokenTag maxValue={ModelTokens[model]} value={totalToken + textTokenCount} />
48
+ </>
49
+ }
50
+ expand={expand}
51
+ footer={<Button icon={<Icon icon={Archive} />} />}
52
+ minHeight={200}
53
+ onExpandChange={setExpand}
54
+ onInputChange={setText}
55
+ onSend={sendMessage}
56
+ />
57
+ </DraggablePanel>
58
+ );
59
+ };
60
+
61
+ export default memo(ChatInput);
@@ -0,0 +1,32 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import ChatInput from '@/pages/chat/[id]/Conversation/Input';
6
+
7
+ import ChatList from './ChatList';
8
+
9
+ const useStyles = createStyles(({ css, token }) => ({
10
+ input: css`
11
+ position: sticky;
12
+ z-index: 10;
13
+ bottom: 0;
14
+ background: ${token.colorBgLayout};
15
+ `,
16
+ }));
17
+
18
+ const Conversation = () => {
19
+ const { styles } = useStyles();
20
+ return (
21
+ <>
22
+ <div style={{ flex: 1, overflowY: 'scroll' }}>
23
+ <ChatList />
24
+ </div>
25
+ <Flexbox className={styles.input}>
26
+ <ChatInput />
27
+ </Flexbox>
28
+ </>
29
+ );
30
+ };
31
+
32
+ export default memo(Conversation);