@lobehub/chat 0.1.6

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 (149) 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 +20 -0
  29. package/README.md +147 -0
  30. package/locales/en_US/common.json +41 -0
  31. package/locales/en_US/setting.json +117 -0
  32. package/locales/zh_CN/common.json +41 -0
  33. package/locales/zh_CN/setting.json +117 -0
  34. package/next.config.mjs +32 -0
  35. package/package.json +140 -0
  36. package/public/next.svg +1 -0
  37. package/public/vercel.svg +1 -0
  38. package/scripts/genDefaultLocale.ts +18 -0
  39. package/scripts/genResources.ts +51 -0
  40. package/scripts/toc.ts +7 -0
  41. package/src/const/fetch.ts +1 -0
  42. package/src/const/modelTokens.ts +8 -0
  43. package/src/features/AvatarWithUpload/index.tsx +27 -0
  44. package/src/features/FolderPanel/index.tsx +55 -0
  45. package/src/features/SideBar/index.tsx +35 -0
  46. package/src/helpers/prompt.test.ts +36 -0
  47. package/src/helpers/prompt.ts +36 -0
  48. package/src/helpers/url.ts +17 -0
  49. package/src/layout/index.tsx +57 -0
  50. package/src/layout/style.ts +18 -0
  51. package/src/locales/create.ts +49 -0
  52. package/src/locales/default/common.ts +41 -0
  53. package/src/locales/default/setting.ts +117 -0
  54. package/src/locales/index.ts +5 -0
  55. package/src/locales/options.ts +19 -0
  56. package/src/locales/resources/en_US.ts +9 -0
  57. package/src/locales/resources/index.ts +13 -0
  58. package/src/locales/resources/zh_CN.ts +9 -0
  59. package/src/migrations/FromV0ToV1.ts +12 -0
  60. package/src/migrations/index.ts +13 -0
  61. package/src/pages/_app.page.tsx +13 -0
  62. package/src/pages/_document.page.tsx +70 -0
  63. package/src/pages/api/LangChainStream.ts +95 -0
  64. package/src/pages/api/chain.api.ts +17 -0
  65. package/src/pages/api/openai.api.ts +31 -0
  66. package/src/pages/chat/SessionList/Header.tsx +56 -0
  67. package/src/pages/chat/SessionList/List/SessionItem.tsx +90 -0
  68. package/src/pages/chat/SessionList/List/index.tsx +31 -0
  69. package/src/pages/chat/SessionList/List/style.ts +77 -0
  70. package/src/pages/chat/SessionList/index.tsx +18 -0
  71. package/src/pages/chat/[id]/Config/ConfigCell.tsx +68 -0
  72. package/src/pages/chat/[id]/Config/ReadMode.tsx +63 -0
  73. package/src/pages/chat/[id]/Config/index.tsx +79 -0
  74. package/src/pages/chat/[id]/Conversation/ChatList.tsx +36 -0
  75. package/src/pages/chat/[id]/Conversation/Input.tsx +61 -0
  76. package/src/pages/chat/[id]/Conversation/index.tsx +32 -0
  77. package/src/pages/chat/[id]/Header.tsx +86 -0
  78. package/src/pages/chat/[id]/edit/AgentConfig.tsx +95 -0
  79. package/src/pages/chat/[id]/edit/AgentMeta.tsx +117 -0
  80. package/src/pages/chat/[id]/edit/FormItem.tsx +26 -0
  81. package/src/pages/chat/[id]/edit/Prompt.tsx +68 -0
  82. package/src/pages/chat/[id]/edit/index.page.tsx +62 -0
  83. package/src/pages/chat/[id]/edit/style.ts +42 -0
  84. package/src/pages/chat/[id]/index.page.tsx +40 -0
  85. package/src/pages/chat/index.page.tsx +1 -0
  86. package/src/pages/chat/layout.tsx +51 -0
  87. package/src/pages/index.page.tsx +1 -0
  88. package/src/pages/setting/Header.tsx +27 -0
  89. package/src/pages/setting/SettingForm.tsx +299 -0
  90. package/src/pages/setting/SliderWithInput/index.tsx +40 -0
  91. package/src/pages/setting/ThemeSwatches/ThemeSwatchesNeutral.tsx +33 -0
  92. package/src/pages/setting/ThemeSwatches/ThemeSwatchesPrimary.tsx +33 -0
  93. package/src/pages/setting/ThemeSwatches/index.ts +2 -0
  94. package/src/pages/setting/index.page.tsx +41 -0
  95. package/src/prompts/agent.ts +65 -0
  96. package/src/services/chatModel.ts +34 -0
  97. package/src/services/langChain.ts +18 -0
  98. package/src/services/url.ts +8 -0
  99. package/src/store/middleware/createHashStorage.ts +49 -0
  100. package/src/store/session/index.ts +33 -0
  101. package/src/store/session/initialState.ts +11 -0
  102. package/src/store/session/selectors.ts +3 -0
  103. package/src/store/session/slices/agentConfig/action.ts +226 -0
  104. package/src/store/session/slices/agentConfig/index.ts +3 -0
  105. package/src/store/session/slices/agentConfig/initialState.ts +34 -0
  106. package/src/store/session/slices/agentConfig/selectors.ts +54 -0
  107. package/src/store/session/slices/chat/action.ts +210 -0
  108. package/src/store/session/slices/chat/index.ts +3 -0
  109. package/src/store/session/slices/chat/initialState.ts +12 -0
  110. package/src/store/session/slices/chat/messageReducer.test.ts +70 -0
  111. package/src/store/session/slices/chat/messageReducer.ts +84 -0
  112. package/src/store/session/slices/chat/selectors.ts +83 -0
  113. package/src/store/session/slices/session/action.ts +118 -0
  114. package/src/store/session/slices/session/index.ts +3 -0
  115. package/src/store/session/slices/session/initialState.ts +31 -0
  116. package/src/store/session/slices/session/reducers/session.test.ts +456 -0
  117. package/src/store/session/slices/session/reducers/session.ts +113 -0
  118. package/src/store/session/slices/session/selectors/chat.ts +4 -0
  119. package/src/store/session/slices/session/selectors/index.ts +20 -0
  120. package/src/store/session/slices/session/selectors/list.ts +65 -0
  121. package/src/store/session/store.ts +17 -0
  122. package/src/store/settings/action.ts +40 -0
  123. package/src/store/settings/index.ts +23 -0
  124. package/src/store/settings/initialState.ts +41 -0
  125. package/src/store/settings/selectors.ts +17 -0
  126. package/src/store/settings/store.ts +13 -0
  127. package/src/styles/antdOverride.ts +33 -0
  128. package/src/styles/global.ts +23 -0
  129. package/src/styles/index.ts +6 -0
  130. package/src/types/chatMessage.ts +46 -0
  131. package/src/types/exportConfig.ts +44 -0
  132. package/src/types/global.d.ts +14 -0
  133. package/src/types/i18next.d.ts +8 -0
  134. package/src/types/langchain.ts +34 -0
  135. package/src/types/llm.ts +49 -0
  136. package/src/types/locale.ts +1 -0
  137. package/src/types/meta.ts +26 -0
  138. package/src/types/openai.ts +62 -0
  139. package/src/types/session.ts +59 -0
  140. package/src/utils/VersionController.test.ts +90 -0
  141. package/src/utils/VersionController.ts +64 -0
  142. package/src/utils/compass.ts +94 -0
  143. package/src/utils/fetch.ts +132 -0
  144. package/src/utils/filter.test.ts +120 -0
  145. package/src/utils/filter.ts +29 -0
  146. package/src/utils/uploadFIle.ts +8 -0
  147. package/src/utils/uuid.ts +9 -0
  148. package/tsconfig.json +31 -0
  149. package/vitest.config.ts +11 -0
@@ -0,0 +1,70 @@
1
+ import { StyleProvider, extractStaticStyle } from 'antd-style';
2
+ import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document';
3
+
4
+ class MyDocument extends Document {
5
+ static async getStaticProps(ctx: DocumentContext) {
6
+ const page = await ctx.renderPage({
7
+ enhanceApp: (App) => (props) =>
8
+ (
9
+ <StyleProvider cache={extractStaticStyle.cache}>
10
+ <App {...props} />
11
+ </StyleProvider>
12
+ ),
13
+ });
14
+
15
+ const styles = extractStaticStyle(page.html).map((item) => item.style);
16
+
17
+ const initialProps = await Document.getInitialProps(ctx);
18
+
19
+ return {
20
+ ...initialProps,
21
+ styles: (
22
+ <>
23
+ {initialProps.styles}
24
+ {styles}
25
+ </>
26
+ ),
27
+ };
28
+ }
29
+
30
+ render() {
31
+ return (
32
+ <Html>
33
+ <Head>
34
+ <link
35
+ href="https://npm.elemecdn.com/@lobehub/assets-favicons/assets/apple-touch-icon.png"
36
+ rel="apple-touch-icon"
37
+ sizes="180x180"
38
+ />
39
+ <link
40
+ href="https://npm.elemecdn.com/@lobehub/assets-favicons/assets/favicon-32x32.png"
41
+ rel="icon"
42
+ sizes="32x32"
43
+ type="image/png"
44
+ />
45
+ <link
46
+ href="https://npm.elemecdn.com/@lobehub/assets-favicons/assets/favicon-16x16.png"
47
+ rel="icon"
48
+ sizes="16x16"
49
+ type="image/png"
50
+ />
51
+ <link
52
+ color="#000000"
53
+ href="https://npm.elemecdn.com/@lobehub/assets-favicons/assets/safari-pinned-tab.svg"
54
+ rel="mask-icon"
55
+ />
56
+ <meta content="LobeHub" name="apple-mobile-web-app-title" />
57
+ <meta content="LobeHub" name="application-name" />
58
+ <meta content="#000000" name="msapplication-TileColor" />
59
+ <meta content="#000000" name="theme-color" />
60
+ </Head>
61
+ <body>
62
+ <Main />
63
+ <NextScript />
64
+ </body>
65
+ </Html>
66
+ );
67
+ }
68
+ }
69
+
70
+ export default MyDocument;
@@ -0,0 +1,95 @@
1
+ import { LLMChain } from 'langchain/chains';
2
+ import { ChatOpenAI } from 'langchain/chat_models/openai';
3
+ import {
4
+ AIMessagePromptTemplate,
5
+ ChatPromptTemplate,
6
+ HumanMessagePromptTemplate,
7
+ SystemMessagePromptTemplate,
8
+ } from 'langchain/prompts';
9
+
10
+ import { LangChainParams } from '@/types/langchain';
11
+
12
+ const isDev = process.env.NODE_ENV === 'development';
13
+ const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL;
14
+
15
+ export function LangChainStream(payload: LangChainParams) {
16
+ const { prompts, vars, llm } = payload;
17
+
18
+ // 将 payload 中的消息转换为 ChatOpenAI 所需的 HumanChatMessage、SystemChatMessage 和 AIChatMessage 类型
19
+ const chatPrompt = ChatPromptTemplate.fromPromptMessages(
20
+ prompts.map((m) => {
21
+ switch (m.role) {
22
+ default:
23
+ case 'user': {
24
+ return HumanMessagePromptTemplate.fromTemplate(m.content);
25
+ }
26
+ case 'system': {
27
+ return SystemMessagePromptTemplate.fromTemplate(m.content);
28
+ }
29
+
30
+ case 'assistant': {
31
+ return AIMessagePromptTemplate.fromTemplate(m.content);
32
+ }
33
+ }
34
+ }),
35
+ );
36
+
37
+ // 使用 TextEncoder 将字符串转换为字节数组,以便在 ReadableStream 中发送
38
+ const encoder = new TextEncoder();
39
+
40
+ // 初始化换行符计数器
41
+
42
+ return new ReadableStream({
43
+ async start(controller) {
44
+ let newlineCounter = 0;
45
+
46
+ const chat = new ChatOpenAI(
47
+ {
48
+ streaming: true,
49
+ ...llm,
50
+
51
+ callbacks: [
52
+ {
53
+ handleLLMNewToken(token) {
54
+ // 如果 message 是换行符,且 newlineCounter 小于 2,那么跳过该换行符
55
+ if (newlineCounter < 2 && token === '\n') {
56
+ return;
57
+ }
58
+
59
+ // 将 message 编码为字节并添加到流中
60
+ const queue = encoder.encode(token);
61
+ controller.enqueue(queue);
62
+ newlineCounter++;
63
+ },
64
+ },
65
+ ],
66
+ // 暂时设定不重试 ,后续看是否需要支持重试
67
+ maxRetries: 0,
68
+ },
69
+ isDev && OPENAI_PROXY_URL ? { basePath: OPENAI_PROXY_URL } : undefined,
70
+ );
71
+
72
+ const chain = new LLMChain({
73
+ callbacks: [
74
+ {
75
+ handleChainError(err: Error): Promise<void> | void {
76
+ console.log(err.message);
77
+ },
78
+ },
79
+ ],
80
+ llm: chat,
81
+ prompt: chatPrompt,
82
+ verbose: true,
83
+ });
84
+ try {
85
+ // 使用转换后的聊天消息作为输入开始聊天
86
+ await chain.call(vars);
87
+ // 完成后,关闭流
88
+ controller.close();
89
+ } catch (error) {
90
+ // 如果在执行过程中发生错误,向流发送错误
91
+ controller.error(error);
92
+ }
93
+ },
94
+ });
95
+ }
@@ -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;