@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.
- package/.changelogrc.js +1 -0
- package/.commitlintrc.js +1 -0
- package/.editorconfig +16 -0
- package/.eslintignore +32 -0
- package/.eslintrc.js +6 -0
- package/.github/ISSUE_TEMPLATE/1_bug_report.yml +45 -0
- package/.github/ISSUE_TEMPLATE/2_feature_request.yml +21 -0
- package/.github/ISSUE_TEMPLATE/3_question.yml +15 -0
- package/.github/ISSUE_TEMPLATE/4_other.md +7 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- package/.github/dependabot.yml +17 -0
- package/.github/workflows/auto-merge.yml +32 -0
- package/.github/workflows/contributor-help.yml +29 -0
- package/.github/workflows/issue-check-inactive.yml +22 -0
- package/.github/workflows/issue-close-require.yml +46 -0
- package/.github/workflows/issue-remove-inactive.yml +25 -0
- package/.github/workflows/release.yml +34 -0
- package/.github/workflows/test.yml +30 -0
- package/.gitpod.yml +3 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +5 -0
- package/.i18nrc.js +13 -0
- package/.prettierignore +63 -0
- package/.prettierrc.js +1 -0
- package/.releaserc.js +1 -0
- package/.remarkrc.js +1 -0
- package/.stylelintrc.js +8 -0
- package/CHANGELOG.md +20 -0
- package/README.md +147 -0
- package/locales/en_US/common.json +41 -0
- package/locales/en_US/setting.json +117 -0
- package/locales/zh_CN/common.json +41 -0
- package/locales/zh_CN/setting.json +117 -0
- package/next.config.mjs +32 -0
- package/package.json +140 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/scripts/genDefaultLocale.ts +18 -0
- package/scripts/genResources.ts +51 -0
- package/scripts/toc.ts +7 -0
- package/src/const/fetch.ts +1 -0
- package/src/const/modelTokens.ts +8 -0
- package/src/features/AvatarWithUpload/index.tsx +27 -0
- package/src/features/FolderPanel/index.tsx +55 -0
- package/src/features/SideBar/index.tsx +35 -0
- package/src/helpers/prompt.test.ts +36 -0
- package/src/helpers/prompt.ts +36 -0
- package/src/helpers/url.ts +17 -0
- package/src/layout/index.tsx +57 -0
- package/src/layout/style.ts +18 -0
- package/src/locales/create.ts +49 -0
- package/src/locales/default/common.ts +41 -0
- package/src/locales/default/setting.ts +117 -0
- package/src/locales/index.ts +5 -0
- package/src/locales/options.ts +19 -0
- package/src/locales/resources/en_US.ts +9 -0
- package/src/locales/resources/index.ts +13 -0
- package/src/locales/resources/zh_CN.ts +9 -0
- package/src/migrations/FromV0ToV1.ts +12 -0
- package/src/migrations/index.ts +13 -0
- package/src/pages/_app.page.tsx +13 -0
- package/src/pages/_document.page.tsx +70 -0
- package/src/pages/api/LangChainStream.ts +95 -0
- package/src/pages/api/chain.api.ts +17 -0
- package/src/pages/api/openai.api.ts +31 -0
- package/src/pages/chat/SessionList/Header.tsx +56 -0
- package/src/pages/chat/SessionList/List/SessionItem.tsx +90 -0
- package/src/pages/chat/SessionList/List/index.tsx +31 -0
- package/src/pages/chat/SessionList/List/style.ts +77 -0
- package/src/pages/chat/SessionList/index.tsx +18 -0
- package/src/pages/chat/[id]/Config/ConfigCell.tsx +68 -0
- package/src/pages/chat/[id]/Config/ReadMode.tsx +63 -0
- package/src/pages/chat/[id]/Config/index.tsx +79 -0
- package/src/pages/chat/[id]/Conversation/ChatList.tsx +36 -0
- package/src/pages/chat/[id]/Conversation/Input.tsx +61 -0
- package/src/pages/chat/[id]/Conversation/index.tsx +32 -0
- package/src/pages/chat/[id]/Header.tsx +86 -0
- package/src/pages/chat/[id]/edit/AgentConfig.tsx +95 -0
- package/src/pages/chat/[id]/edit/AgentMeta.tsx +117 -0
- package/src/pages/chat/[id]/edit/FormItem.tsx +26 -0
- package/src/pages/chat/[id]/edit/Prompt.tsx +68 -0
- package/src/pages/chat/[id]/edit/index.page.tsx +62 -0
- package/src/pages/chat/[id]/edit/style.ts +42 -0
- package/src/pages/chat/[id]/index.page.tsx +40 -0
- package/src/pages/chat/index.page.tsx +1 -0
- package/src/pages/chat/layout.tsx +51 -0
- package/src/pages/index.page.tsx +1 -0
- package/src/pages/setting/Header.tsx +27 -0
- package/src/pages/setting/SettingForm.tsx +299 -0
- package/src/pages/setting/SliderWithInput/index.tsx +40 -0
- package/src/pages/setting/ThemeSwatches/ThemeSwatchesNeutral.tsx +33 -0
- package/src/pages/setting/ThemeSwatches/ThemeSwatchesPrimary.tsx +33 -0
- package/src/pages/setting/ThemeSwatches/index.ts +2 -0
- package/src/pages/setting/index.page.tsx +41 -0
- package/src/prompts/agent.ts +65 -0
- package/src/services/chatModel.ts +34 -0
- package/src/services/langChain.ts +18 -0
- package/src/services/url.ts +8 -0
- package/src/store/middleware/createHashStorage.ts +49 -0
- package/src/store/session/index.ts +33 -0
- package/src/store/session/initialState.ts +11 -0
- package/src/store/session/selectors.ts +3 -0
- package/src/store/session/slices/agentConfig/action.ts +226 -0
- package/src/store/session/slices/agentConfig/index.ts +3 -0
- package/src/store/session/slices/agentConfig/initialState.ts +34 -0
- package/src/store/session/slices/agentConfig/selectors.ts +54 -0
- package/src/store/session/slices/chat/action.ts +210 -0
- package/src/store/session/slices/chat/index.ts +3 -0
- package/src/store/session/slices/chat/initialState.ts +12 -0
- package/src/store/session/slices/chat/messageReducer.test.ts +70 -0
- package/src/store/session/slices/chat/messageReducer.ts +84 -0
- package/src/store/session/slices/chat/selectors.ts +83 -0
- package/src/store/session/slices/session/action.ts +118 -0
- package/src/store/session/slices/session/index.ts +3 -0
- package/src/store/session/slices/session/initialState.ts +31 -0
- package/src/store/session/slices/session/reducers/session.test.ts +456 -0
- package/src/store/session/slices/session/reducers/session.ts +113 -0
- package/src/store/session/slices/session/selectors/chat.ts +4 -0
- package/src/store/session/slices/session/selectors/index.ts +20 -0
- package/src/store/session/slices/session/selectors/list.ts +65 -0
- package/src/store/session/store.ts +17 -0
- package/src/store/settings/action.ts +40 -0
- package/src/store/settings/index.ts +23 -0
- package/src/store/settings/initialState.ts +41 -0
- package/src/store/settings/selectors.ts +17 -0
- package/src/store/settings/store.ts +13 -0
- package/src/styles/antdOverride.ts +33 -0
- package/src/styles/global.ts +23 -0
- package/src/styles/index.ts +6 -0
- package/src/types/chatMessage.ts +46 -0
- package/src/types/exportConfig.ts +44 -0
- package/src/types/global.d.ts +14 -0
- package/src/types/i18next.d.ts +8 -0
- package/src/types/langchain.ts +34 -0
- package/src/types/llm.ts +49 -0
- package/src/types/locale.ts +1 -0
- package/src/types/meta.ts +26 -0
- package/src/types/openai.ts +62 -0
- package/src/types/session.ts +59 -0
- package/src/utils/VersionController.test.ts +90 -0
- package/src/utils/VersionController.ts +64 -0
- package/src/utils/compass.ts +94 -0
- package/src/utils/fetch.ts +132 -0
- package/src/utils/filter.test.ts +120 -0
- package/src/utils/filter.ts +29 -0
- package/src/utils/uploadFIle.ts +8 -0
- package/src/utils/uuid.ts +9 -0
- package/tsconfig.json +31 -0
- 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;
|