diff --git a/.changelogrc.js b/.changelogrc.js new file mode 100644 index 000000000000..9a2f5f98d8cd --- /dev/null +++ b/.changelogrc.js @@ -0,0 +1 @@ +module.exports = require('@lobehub/lint').changelog; diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 000000000000..9b8c6ace5a00 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1 @@ +module.exports = require('@lobehub/lint').commitlint; diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..7e3649acc2c1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000000..b24520330465 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,32 @@ +# Eslintignore for LobeHub +################################################################ + +# dependencies +node_modules + +# ci +coverage +.coverage + +# test +jest* +_test_ +__test__ +*.test.ts + +# umi +.umi +.umi-production +.umi-test +.dumi/tmp* +!.dumirc.ts + +# production +dist +es +lib +logs + +# misc +# add other ignore file below +.next \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index b2d8f1a1ff46..920af1d9c092 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,13 +3,4 @@ const config = require('@lobehub/lint').eslint; config.extends.push('plugin:@next/next/recommended'); //config.extends.push('plugin:@next/next/core-web-vitals'); -module.exports = { - ...config, - rules: { - ...config.rules, - 'react/jsx-sort-props': 'off', - 'sort-keys-fix/sort-keys-fix': 'off', - 'typescript-sort-keys/interface': 'off', - 'unicorn/switch-case-braces': 'off', - }, -}; +module.exports = config; diff --git a/.github/ISSUE_TEMPLATE/1_bug_report.yml b/.github/ISSUE_TEMPLATE/1_bug_report.yml index c8aa08e70d1e..d181c3879815 100644 --- a/.github/ISSUE_TEMPLATE/1_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1_bug_report.yml @@ -1,11 +1,11 @@ -name: "🐛 反馈缺陷 Bug Report" -description: "反馈一个问题缺陷 | Report an bug" -title: "[Bug] " -labels: "🐛 Bug" +name: '🐛 反馈缺陷 Bug Report' +description: '反馈一个问题缺陷 | Report an bug' +title: '[Bug] ' +labels: '🐛 Bug' body: - type: dropdown attributes: - label: "💻 系统环境 | Operating System" + label: '💻 系统环境 | Operating System' options: - Windows - macOS @@ -16,7 +16,7 @@ body: required: true - type: dropdown attributes: - label: "🌐 浏览器 | Browser" + label: '🌐 浏览器 | Browser' options: - Chrome - Edge @@ -27,19 +27,19 @@ body: required: true - type: textarea attributes: - label: "🐛 问题描述 | Bug Description" + label: '🐛 问题描述 | Bug Description' description: A clear and concise description of the bug. validations: required: true - type: textarea attributes: - label: "🚦 期望结果 | Expected Behavior" + label: '🚦 期望结果 | Expected Behavior' description: A clear and concise description of what you expected to happen. - type: textarea attributes: - label: "📷 复现步骤 | Recurrence Steps" + label: '📷 复现步骤 | Recurrence Steps' description: A clear and concise description of how to recurrence. - type: textarea attributes: - label: "📝 补充信息 | Additional Information" + label: '📝 补充信息 | Additional Information' description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here. diff --git a/.github/ISSUE_TEMPLATE/2_feature_request.yml b/.github/ISSUE_TEMPLATE/2_feature_request.yml index b1a9d1e219d3..edcf7d0643cc 100644 --- a/.github/ISSUE_TEMPLATE/2_feature_request.yml +++ b/.github/ISSUE_TEMPLATE/2_feature_request.yml @@ -1,21 +1,21 @@ -name: "🌠 功能需求 Feature Request" -description: "需求或建议 | Suggest an idea" -title: "[Request] " -labels: "🌠 Feature Request" +name: '🌠 功能需求 Feature Request' +description: '需求或建议 | Suggest an idea' +title: '[Request] ' +labels: '🌠 Feature Request' body: - type: textarea attributes: - label: "🥰 需求描述 | Feature Description" + label: '🥰 需求描述 | Feature Description' description: Please add a clear and concise description of the problem you are seeking to solve with this feature request. validations: required: true - type: textarea attributes: - label: "🧐 解决方案 | Proposed Solution" + label: '🧐 解决方案 | Proposed Solution' description: Describe the solution you'd like in a clear and concise manner. validations: required: true - type: textarea attributes: - label: "📝 补充信息 | Additional Information" + label: '📝 补充信息 | Additional Information' description: Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/3_question.yml b/.github/ISSUE_TEMPLATE/3_question.yml index 687666d0f03f..f989f7d11be9 100644 --- a/.github/ISSUE_TEMPLATE/3_question.yml +++ b/.github/ISSUE_TEMPLATE/3_question.yml @@ -1,15 +1,15 @@ -name: "😇 疑问或帮助 Help Wanted" -description: "疑问或需要帮助 | Need help" -title: "[Question] " -labels: "😇 Help Wanted" +name: '😇 疑问或帮助 Help Wanted' +description: '疑问或需要帮助 | Need help' +title: '[Question] ' +labels: '😇 Help Wanted' body: - type: textarea attributes: - label: "🧐 问题描述 | Proposed Solution" + label: '🧐 问题描述 | Proposed Solution' description: A clear and concise description of the proplem. validations: required: true - type: textarea attributes: - label: "📝 补充信息 | Additional Information" + label: '📝 补充信息 | Additional Information' description: Add any other context about the problem here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5702943a41f7..1f8319428a3a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,17 +1,17 @@ version: 2 updates: - package-ecosystem: npm - directory: "/" + directory: '/' schedule: interval: weekly - time: "19:00" + time: '19:00' timezone: 'Asia/Shanghai' open-pull-requests-limit: 10 versioning-strategy: increase - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: 'github-actions' + directory: '/' schedule: interval: monthly - time: "19:00" + time: '19:00' timezone: 'Asia/Shanghai' diff --git a/.github/workflows/contributor-help.yml b/.github/workflows/contributor-help.yml index 993060660916..fd68fec3bd42 100644 --- a/.github/workflows/contributor-help.yml +++ b/.github/workflows/contributor-help.yml @@ -2,7 +2,7 @@ name: Contributor Helper on: # 🌏 Think about the planet! No need to update stats too frequently - schedule: [{cron: "0 18 * * *"}] + schedule: [{ cron: '0 18 * * *' }] # 💡 The following line lets you run workflow manually from the action tab! workflow_dispatch: jobs: diff --git a/.github/workflows/issue-check-inactive.yml b/.github/workflows/issue-check-inactive.yml index 2621364ba55a..d37c4c307197 100644 --- a/.github/workflows/issue-check-inactive.yml +++ b/.github/workflows/issue-check-inactive.yml @@ -2,7 +2,7 @@ name: Issue Check Inactive on: schedule: - - cron: "0 0 */15 * *" + - cron: '0 0 */15 * *' permissions: contents: read @@ -10,8 +10,8 @@ permissions: jobs: issue-check-inactive: permissions: - issues: write # for actions-cool/issues-helper to update issues - pull-requests: write # for actions-cool/issues-helper to update PRs + issues: write # for actions-cool/issues-helper to update issues + pull-requests: write # for actions-cool/issues-helper to update PRs runs-on: ubuntu-latest steps: - name: check-inactive @@ -19,4 +19,4 @@ jobs: with: actions: 'check-inactive' inactive-label: 'Inactive' - inactive-day: 30 \ No newline at end of file + inactive-day: 30 diff --git a/.github/workflows/issue-close-require.yml b/.github/workflows/issue-close-require.yml index 1b9239790192..68d6b6cefe85 100644 --- a/.github/workflows/issue-close-require.yml +++ b/.github/workflows/issue-close-require.yml @@ -2,7 +2,7 @@ name: Issue Close Require on: schedule: - - cron: "0 0 * * *" + - cron: '0 0 * * *' permissions: contents: read @@ -10,8 +10,8 @@ permissions: jobs: issue-close-require: permissions: - issues: write # for actions-cool/issues-helper to update issues - pull-requests: write # for actions-cool/issues-helper to update PRs + issues: write # for actions-cool/issues-helper to update issues + pull-requests: write # for actions-cool/issues-helper to update PRs runs-on: ubuntu-latest steps: - name: need reproduce diff --git a/.github/workflows/issue-remove-inactive.yml b/.github/workflows/issue-remove-inactive.yml index 3b25e9d0eb52..dbe42ddb86d2 100644 --- a/.github/workflows/issue-remove-inactive.yml +++ b/.github/workflows/issue-remove-inactive.yml @@ -12,8 +12,8 @@ permissions: jobs: issue-remove-inactive: permissions: - issues: write # for actions-cool/issues-helper to update issues - pull-requests: write # for actions-cool/issues-helper to update PRs + issues: write # for actions-cool/issues-helper to update issues + pull-requests: write # for actions-cool/issues-helper to update PRs runs-on: ubuntu-latest steps: - name: remove inactive @@ -22,4 +22,4 @@ jobs: with: actions: 'remove-labels' issue-number: ${{ github.event.issue.number }} - labels: 'Inactive' \ No newline at end of file + labels: 'Inactive' diff --git a/.gitignore b/.gitignore index 4577ee4f2843..0d17fd564a7c 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,3 @@ test-output *.tsbuildinfo next-env.d.ts .next -.env diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000000..e605228076a1 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,3 @@ +tasks: + - init: pnpm install + command: pnpm run start diff --git a/.npmrc b/.npmrc index 89cfc9f9cf10..d9ed3d371eb9 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,11 @@ lockfile=false resolution-mode=highest +public-hoist-pattern[]=*@umijs/lint* +public-hoist-pattern[]=*changelog* +public-hoist-pattern[]=*commitlint* +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=*prettier* +public-hoist-pattern[]=*remark* +public-hoist-pattern[]=*semantic-release* +public-hoist-pattern[]=*stylelint* diff --git a/.prettierignore b/.prettierignore index 2bfa66c3a0be..3e459cbe4796 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,28 +1,63 @@ -**/*.svg -.umi -.umi-production -/dist -.dockerignore +# Prettierignore for LobeHub +################################################################ + +# general .DS_Store -.eslintignore -*.png -*.jpg -*.webp -*.toml -*.py -docker .editorconfig -Dockerfile* -.gitignore -.prettierignore -LICENSE -.eslintcache -*.lock -yarn-error.log .idea +.vscode +.history +.temp +.env.local .husky .npmrc -.env.local -.next +.gitkeep +venv +temp +tmp +LICENSE + +# dependencies +node_modules +*.log +*.lock +package-lock.json + +# ci +coverage +.coverage +.eslintcache +.stylelintcache +test-output __snapshots__ -.snap \ No newline at end of file +*.snap + +# production +dist +es +lib +logs + +# umi +.umi +.umi-production +.umi-test +.dumi/tmp* + +# ignore files +.*ignore + +# docker +docker +Dockerfile* + +# image +*.webp +*.gif +*.png +*.jpg +*.svg + +# misc +# add other ignore file below +.next \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js index 8be3baf87a51..f0355a9c1a75 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,10 +1 @@ -module.exports = { - printWidth: 120, - singleQuote: true, - trailingComma: 'all', - proseWrap: 'never', - endOfLine: 'lf', - overrides: [{ files: '.prettierrc', options: { parser: 'json' } }], - plugins: [require.resolve('prettier-plugin-packagejson'), require.resolve('prettier-plugin-organize-imports')], - pluginSearchDirs: false, -}; +module.exports = require('@lobehub/lint').prettier; diff --git a/.releaserc.js b/.releaserc.js index 7b278591ede9..37930011d9d4 100644 --- a/.releaserc.js +++ b/.releaserc.js @@ -1,4 +1 @@ -module.exports = { - extends: ['semantic-release-config-gitmoji'], - branches: ['master'], -}; +module.exports = require('@lobehub/lint').semanticRelease; diff --git a/.remarkrc.js b/.remarkrc.js new file mode 100644 index 000000000000..b673c10ed1cc --- /dev/null +++ b/.remarkrc.js @@ -0,0 +1 @@ +module.exports = require('@lobehub/lint').remarklint; diff --git a/.stylelintrc.js b/.stylelintrc.js index bbd3844f4c25..c40700dd91da 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -1,10 +1,8 @@ +const config = require('@lobehub/lint').stylelint; + module.exports = { - extends: ['stylelint-config-recommended', 'stylelint-config-clean-order'], - files: ['*.js', '*.jsx', '*.ts', '*.tsx'], - plugins: ['stylelint-order'], - customSyntax: 'postcss-styled-syntax', + ...config, rules: { - 'no-empty-source': null, - 'no-invalid-double-slash-comments': null, + 'selector-id-pattern': null, }, }; diff --git a/README.md b/README.md index c07c25a46fac..57078fd04bdd 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Or clone it for local development: ```bash $ git clone https://github.com/lobehub/lobe-chat.git -$ cd canisminor-template +$ cd lobe-chat $ pnpm install $ pnpm start ``` diff --git a/package.json b/package.json index 112d76c0b56f..7f5399f1b79d 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "sideEffects": false, "scripts": { "build": "next build", - "dev": "PORT=3010 next dev", + "dev": "next dev -p 3010", "lint": "eslint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix", "lint:md": "remark . --quiet --frail --output", "lint:style": "stylelint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix", @@ -88,7 +88,7 @@ }, "devDependencies": { "@commitlint/cli": "^17", - "@lobehub/lint": "^1", + "@lobehub/lint": "latest", "@next/eslint-plugin-next": "^13", "@testing-library/jest-dom": "^5", "@testing-library/react": "^14", @@ -110,8 +110,8 @@ "node-fetch": "^3", "postcss-styled-syntax": "^0.4", "prettier": "^2", - "prettier-plugin-organize-imports": "^3", - "prettier-plugin-packagejson": "^2", + "remark": "^14", + "remark-cli": "^11", "semantic-release": "^21", "semantic-release-config-gitmoji": "^1", "stylelint": "^15", diff --git a/src/features/FolderPanel/index.tsx b/src/features/FolderPanel/index.tsx index d9f9c31e580d..9bd4c7181141 100644 --- a/src/features/FolderPanel/index.tsx +++ b/src/features/FolderPanel/index.tsx @@ -15,17 +15,26 @@ export const useStyles = createStyles(({ css, token }) => ({ export default ({ children }: PropsWithChildren) => { const { styles } = useStyles(); - const [sessionsWidth, sessionExpandable] = useSettings((s) => [s.sessionsWidth, s.sessionExpandable], shallow); + const [sessionsWidth, sessionExpandable] = useSettings( + (s) => [s.sessionsWidth, s.sessionExpandable], + shallow, + ); const [tmpWidth, setWidth] = useState(sessionsWidth); if (tmpWidth !== sessionsWidth) setWidth(sessionsWidth); return ( { + useSettings.setState({ + sessionExpandable: expand, + sessionsWidth: expand ? 320 : 0, + }); + }} onSizeChange={(_, size) => { if (!size) return; @@ -36,14 +45,8 @@ export default ({ children }: PropsWithChildren) => { setWidth(nextWidth); useSettings.setState({ sessionsWidth: nextWidth }); }} - expand={sessionExpandable} - onExpandChange={(expand) => { - useSettings.setState({ - sessionsWidth: expand ? 320 : 0, - sessionExpandable: expand, - }); - }} - className={styles.panel} + placement="left" + size={{ height: '100vh', width: sessionsWidth }} > {children} diff --git a/src/helpers/prompt.test.ts b/src/helpers/prompt.test.ts index ee180dcc7ec3..10a6036883e6 100644 --- a/src/helpers/prompt.test.ts +++ b/src/helpers/prompt.test.ts @@ -1,6 +1,7 @@ -import { getInputVariablesFromMessages } from '@/helpers/prompt'; import { ChatMessage } from '@lobehub/ui'; +import { getInputVariablesFromMessages } from '@/helpers/prompt'; + describe('getInputVariablesFromMessages 方法', () => { it('应当在输入为空数组时返回空数组', () => { const result = getInputVariablesFromMessages([]); diff --git a/src/helpers/prompt.ts b/src/helpers/prompt.ts index ce72da1f4c2b..9719d7bef21b 100644 --- a/src/helpers/prompt.ts +++ b/src/helpers/prompt.ts @@ -11,14 +11,17 @@ export const getChatPromptTemplate = (chatMessages: ChatMessage[]) => chatMessages.map((m) => { switch (m.role) { default: - case 'user': + case 'user': { return HumanMessagePromptTemplate.fromTemplate(m.content); + } - case 'system': + case 'system': { return SystemMessagePromptTemplate.fromTemplate(m.content); + } - case 'assistant': + case 'assistant': { return AIMessagePromptTemplate.fromTemplate(m.content); + } } }), ); diff --git a/src/helpers/url.ts b/src/helpers/url.ts index f01e9d29f4a1..2b255303c884 100644 --- a/src/helpers/url.ts +++ b/src/helpers/url.ts @@ -1,8 +1,11 @@ -import { Compressor } from '@/utils/compass'; import { ChatMessage } from '@lobehub/ui'; +import { Compressor } from '@/utils/compass'; + export const genShareMessagesUrl = (messages: ChatMessage[], systemRole?: string) => { - const compassedMsg = systemRole ? [{ role: 'system', content: systemRole }, ...messages] : messages; + const compassedMsg = systemRole + ? [{ content: systemRole, role: 'system' }, ...messages] + : messages; return `/share?messages=${Compressor.compress(JSON.stringify(compassedMsg))}`; }; diff --git a/src/layout/index.tsx b/src/layout/index.tsx index 8f40938eaea4..ea0c164c810d 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -1,11 +1,10 @@ import { ThemeProvider } from '@lobehub/ui'; import { App, ConfigProvider } from 'antd'; import 'antd/dist/reset.css'; - import Zh_CN from 'antd/locale/zh_CN'; import { PropsWithChildren, useEffect } from 'react'; - import { useChatStore } from 'src/store/session'; + import { GlobalStyle, useStyles } from './style'; const Layout = ({ children }: PropsWithChildren) => { diff --git a/src/layout/style.ts b/src/layout/style.ts index fc2acd35e65f..b0e8dd5e9be1 100644 --- a/src/layout/style.ts +++ b/src/layout/style.ts @@ -16,7 +16,7 @@ export const useStyles = createStyles(({ css, token }) => ({ background-image: linear-gradient( 180deg, ${token.colorBgContainer} 0%, - rgba(255, 255, 255, 0) 20% + rgba(255, 255, 255, 0%) 20% ); :has(#ChatLayout, #FlowLayout) { diff --git a/src/pages/_app.page.tsx b/src/pages/_app.page.tsx index a99a22d46402..653c73be705b 100644 --- a/src/pages/_app.page.tsx +++ b/src/pages/_app.page.tsx @@ -1,7 +1,8 @@ -import Layout from '@/layout'; import { Analytics } from '@vercel/analytics/react'; import type { AppProps } from 'next/app'; +import Layout from '@/layout'; + function MyApp({ Component, pageProps }: AppProps) { return ( diff --git a/src/pages/_document.page.tsx b/src/pages/_document.page.tsx index 09db63c5fcb2..5b19b2d82294 100644 --- a/src/pages/_document.page.tsx +++ b/src/pages/_document.page.tsx @@ -1,4 +1,4 @@ -import { extractStaticStyle, StyleProvider } from 'antd-style'; +import { StyleProvider, extractStaticStyle } from 'antd-style'; import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document'; class MyDocument extends Document { diff --git a/src/pages/api/LangChainStream.ts b/src/pages/api/LangChainStream.ts index 2b14a2767a1c..69f0c19d1afb 100644 --- a/src/pages/api/LangChainStream.ts +++ b/src/pages/api/LangChainStream.ts @@ -1,4 +1,3 @@ -import { LangChainParams } from '@/types/langchain'; import { LLMChain } from 'langchain/chains'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { @@ -8,6 +7,8 @@ import { SystemMessagePromptTemplate, } from 'langchain/prompts'; +import { LangChainParams } from '@/types/langchain'; + const isDev = process.env.NODE_ENV === 'development'; const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL; @@ -19,13 +20,16 @@ export function LangChainStream(payload: LangChainParams) { prompts.map((m) => { switch (m.role) { default: - case 'user': + case 'user': { return HumanMessagePromptTemplate.fromTemplate(m.content); - case 'system': + } + case 'system': { return SystemMessagePromptTemplate.fromTemplate(m.content); + } - case 'assistant': + case 'assistant': { return AIMessagePromptTemplate.fromTemplate(m.content); + } } }), ); @@ -43,8 +47,7 @@ export function LangChainStream(payload: LangChainParams) { { streaming: true, ...llm, - // 暂时设定不重试 ,后续看是否需要支持重试 - maxRetries: 0, + callbacks: [ { handleLLMNewToken(token) { @@ -60,14 +63,13 @@ export function LangChainStream(payload: LangChainParams) { }, }, ], + // 暂时设定不重试 ,后续看是否需要支持重试 + maxRetries: 0, }, isDev && OPENAI_PROXY_URL ? { basePath: OPENAI_PROXY_URL } : undefined, ); const chain = new LLMChain({ - prompt: chatPrompt, - llm: chat, - verbose: true, callbacks: [ { handleChainError(err: Error): Promise | void { @@ -75,6 +77,9 @@ export function LangChainStream(payload: LangChainParams) { }, }, ], + llm: chat, + prompt: chatPrompt, + verbose: true, }); try { // 使用转换后的聊天消息作为输入开始聊天 diff --git a/src/pages/api/OpenAIStream.ts b/src/pages/api/OpenAIStream.ts index 678030de8d3a..8704de701ac9 100644 --- a/src/pages/api/OpenAIStream.ts +++ b/src/pages/api/OpenAIStream.ts @@ -1,7 +1,8 @@ -import { OpenAIChatMessage } from '@/types/openai'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { AIChatMessage, HumanChatMessage, SystemChatMessage } from 'langchain/schema'; +import { OpenAIChatMessage } from '@/types/openai'; + const isDev = process.env.NODE_ENV === 'development'; const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL; @@ -10,46 +11,46 @@ const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL; */ export interface OpenAIStreamPayload { /** - * @title 模型名称 + * @title 控制生成文本中的惩罚系数,用于减少重复性 + * @default 0 */ - model: string; + frequency_penalty?: number; /** - * @title 聊天信息列表 + * @title 生成文本的最大长度 */ - messages: OpenAIChatMessage[]; + max_tokens?: number; /** - * @title 生成文本的随机度量,用于控制文本的创造性和多样性 - * @default 0.5 + * @title 聊天信息列表 */ - temperature: number; + messages: OpenAIChatMessage[]; /** - * @title 控制生成文本中最高概率的单个令牌 - * @default 1 + * @title 模型名称 */ - top_p?: number; + model: string; /** - * @title 控制生成文本中的惩罚系数,用于减少重复性 - * @default 0 + * @title 返回的文本数量 */ - frequency_penalty?: number; + n?: number; /** * @title 控制生成文本中的惩罚系数,用于减少主题的变化 * @default 0 */ presence_penalty?: number; - /** - * @title 生成文本的最大长度 - */ - max_tokens?: number; /** * @title 是否开启流式请求 * @default true */ stream?: boolean; /** - * @title 返回的文本数量 + * @title 生成文本的随机度量,用于控制文本的创造性和多样性 + * @default 0.5 */ - n?: number; + temperature: number; + /** + * @title 控制生成文本中最高概率的单个令牌 + * @default 1 + */ + top_p?: number; } export function OpenAIStream(payload: OpenAIStreamPayload) { @@ -59,13 +60,16 @@ export function OpenAIStream(payload: OpenAIStreamPayload) { const chatMessages = messages.map((m) => { switch (m.role) { default: - case 'user': + case 'user': { return new HumanChatMessage(m.content); - case 'system': + } + case 'system': { return new SystemChatMessage(m.content); + } - case 'assistant': + case 'assistant': { return new AIChatMessage(m.content); + } } }); @@ -82,9 +86,7 @@ export function OpenAIStream(payload: OpenAIStreamPayload) { { streaming: true, ...params, - // 暂时设定不重试 ,后续看是否需要支持重试 - maxRetries: 0, - verbose: true, + callbacks: [ { handleLLMNewToken(token) { @@ -100,6 +102,9 @@ export function OpenAIStream(payload: OpenAIStreamPayload) { }, }, ], + // 暂时设定不重试 ,后续看是否需要支持重试 + maxRetries: 0, + verbose: true, }, isDev && OPENAI_PROXY_URL ? { basePath: OPENAI_PROXY_URL } : undefined, ); diff --git a/src/pages/chat/Config/ConfigCell.tsx b/src/pages/chat/Config/ConfigCell.tsx index 93fa2fe6a6bf..d4c8c3c743c8 100644 --- a/src/pages/chat/Config/ConfigCell.tsx +++ b/src/pages/chat/Config/ConfigCell.tsx @@ -24,8 +24,13 @@ export type ConfigCellProps = ConfigItem; export const ConfigCell = memo(({ icon, label, value }) => { const { styles } = useStyles(); return ( - - + + {label} @@ -45,13 +50,13 @@ export const ConfigCellGroup = memo(({ items }) => { {items.map(({ label, icon, value }, index) => ( - + {label} diff --git a/src/pages/chat/Config/ReadMode.tsx b/src/pages/chat/Config/ReadMode.tsx index 55e291d47a50..ea0823160f75 100644 --- a/src/pages/chat/Config/ReadMode.tsx +++ b/src/pages/chat/Config/ReadMode.tsx @@ -1,4 +1,3 @@ -import { agentSelectors, sessionSelectors, useChatStore } from '@/store/session'; import { Avatar } from '@lobehub/ui'; import { createStyles } from 'antd-style'; import isEqual from 'fast-deep-equal'; @@ -7,19 +6,21 @@ import { memo } from 'react'; import { Center, Flexbox } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; +import { agentSelectors, sessionSelectors, useChatStore } from '@/store/session'; + import { ConfigCell, ConfigCellGroup } from './ConfigCell'; const useStyles = createStyles(({ css, token }) => ({ - title: css` - font-size: ${token.fontSizeHeading4}px; - font-weight: bold; - `, desc: css` color: ${token.colorText}; `, model: css` color: ${token.colorTextTertiary}; `, + title: css` + font-size: ${token.fontSizeHeading4}px; + font-weight: bold; + `, })); const ReadMode = memo(() => { @@ -30,13 +31,13 @@ const ReadMode = memo(() => { const model = useChatStore(agentSelectors.currentAgentModel, shallow); return ( -
- +
+ {title || '默认对话'} {model} {session.meta.description} - + ({ @@ -18,29 +19,38 @@ const useStyles = createStyles(({ css, token }) => ({ const Config = () => { const { styles } = useStyles(); - const [showAgentSettings, toggleConfig] = useChatStore((s) => [s.showAgentSettings, s.toggleConfig], shallow); + const [showAgentSettings, toggleConfig] = useChatStore( + (s) => [s.showAgentSettings, s.toggleConfig], + shallow, + ); return ( - + 会话设置 { toggleConfig(false); }} + title={'关闭'} /> diff --git a/src/pages/chat/Conversation/ChatList.tsx b/src/pages/chat/Conversation/ChatList.tsx index a9aa688f3e15..5b9cfdfca01f 100644 --- a/src/pages/chat/Conversation/ChatList.tsx +++ b/src/pages/chat/Conversation/ChatList.tsx @@ -7,20 +7,25 @@ import { chatSelectors, useChatStore } from '@/store/session'; const List = () => { const data = useChatStore(chatSelectors.currentChats, isEqual); - const [deleteMessage, resendMessage] = useChatStore((s) => [s.deleteMessage, s.resendMessage], shallow); + const [deleteMessage, resendMessage] = useChatStore( + (s) => [s.deleteMessage, s.resendMessage], + shallow, + ); return ( { switch (key) { - case 'delete': + case 'delete': { deleteMessage(id); break; + } - case 'regenerate': + case 'regenerate': { resendMessage(id); break; + } } }} style={{ marginTop: 24 }} diff --git a/src/pages/chat/Conversation/Input.tsx b/src/pages/chat/Conversation/Input.tsx index 30b11ae37e71..c52ef7b9a387 100644 --- a/src/pages/chat/Conversation/Input.tsx +++ b/src/pages/chat/Conversation/Input.tsx @@ -16,7 +16,12 @@ const ChatInput = () => { const [inputHeight] = useSettings((s) => [s.inputHeight], shallow); const [totalToken, model, sendMessage, clearMessage] = useChatStore( - (s) => [chatSelectors.totalTokenCount(s), agentSelectors.currentAgentModel(s), s.sendMessage, s.clearMessage], + (s) => [ + chatSelectors.totalTokenCount(s), + agentSelectors.currentAgentModel(s), + s.sendMessage, + s.clearMessage, + ], shallow, ); @@ -25,14 +30,14 @@ const ChatInput = () => { expandable={false} fullscreen={expand} minHeight={200} - placement="bottom" - size={{ width: '100%', height: inputHeight }} onSizeChange={(_, size) => { if (!size) return; useSettings.setState({ inputHeight: typeof size.height === 'string' ? Number.parseInt(size.height) : size.height, }); }} + placement="bottom" + size={{ height: inputHeight, width: '100%' }} > ({ - title: css` - font-weight: bold; - color: ${token.colorText}; - `, desc: css` font-size: 12px; color: ${token.colorTextTertiary}; `, + title: css` + font-weight: bold; + color: ${token.colorText}; + `, })); const Header = memo(() => { const theme = useTheme(); @@ -38,9 +39,9 @@ const Header = memo(() => { const { styles } = useStyles(); return ( { gridArea: 'header', }} > - - + + {meta?.title} {meta?.description || '暂无描述'} - + { // genShareUrl(); }} + title={'分享'} /> - + diff --git a/src/pages/chat/SessionList/Header.tsx b/src/pages/chat/SessionList/Header.tsx index 42090646205e..c610698e648f 100644 --- a/src/pages/chat/SessionList/Header.tsx +++ b/src/pages/chat/SessionList/Header.tsx @@ -1,12 +1,12 @@ import { ActionIcon, Logo, SearchBar } from '@lobehub/ui'; import { createStyles } from 'antd-style'; import { Plus } from 'lucide-react'; +import Link from 'next/link'; import { memo } from 'react'; import { Flexbox } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; import { useChatStore } from '@/store/session'; -import Link from 'next/link'; export const useStyles = createStyles(({ css, token }) => ({ logo: css` @@ -21,22 +21,25 @@ export const useStyles = createStyles(({ css, token }) => ({ const Header = memo(() => { const { styles } = useStyles(); - const [keywords, createSession] = useChatStore((s) => [s.searchKeywords, s.createSession], shallow); + const [keywords, createSession] = useChatStore( + (s) => [s.searchKeywords, s.createSession], + shallow, + ); return ( - - + + - + - + useChatStore.setState({ searchKeywords: e.target.value })} placeholder="Search..." type={'block'} - onChange={(e) => useChatStore.setState({ searchKeywords: e.target.value })} + value={keywords} /> ); diff --git a/src/pages/chat/SessionList/List/SessionItem.tsx b/src/pages/chat/SessionList/List/SessionItem.tsx index a30a06210bc2..7efe9af0bfc3 100644 --- a/src/pages/chat/SessionList/List/SessionItem.tsx +++ b/src/pages/chat/SessionList/List/SessionItem.tsx @@ -1,12 +1,12 @@ +import { CloseOutlined } from '@ant-design/icons'; import { Avatar, List } from '@lobehub/ui'; +import { Popconfirm } from 'antd'; import { createStyles } from 'antd-style'; import { FC, memo } from 'react'; import { Flexbox } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; import { sessionSelectors, useChatStore } from '@/store/session'; -import { CloseOutlined } from '@ant-design/icons'; -import { Popconfirm } from 'antd'; const useStyles = createStyles(({ css, cx }) => { const closeCtn = css` @@ -23,6 +23,10 @@ const useStyles = createStyles(({ css, cx }) => { opacity: 0; `; return { + active: css` + opacity: 1; + `, + closeCtn, container: css` position: relative; @@ -35,10 +39,6 @@ const useStyles = createStyles(({ css, cx }) => { time: css` align-self: flex-start; `, - closeCtn, - active: css` - opacity: 1; - `, }; }); @@ -51,43 +51,51 @@ interface SessionItemProps { const SessionItem: FC = memo(({ id, active, simple = true, loading }) => { const { styles, theme, cx } = useStyles(); - const [title, systemRole, avatar, avatarBackground, updateAt, switchAgent, removeSession] = useChatStore((s) => { - const session = sessionSelectors.getSessionById(id)(s); - const meta = session.meta; + const [title, systemRole, avatar, avatarBackground, updateAt, switchAgent, removeSession] = + useChatStore((s) => { + const session = sessionSelectors.getSessionById(id)(s); + const meta = session.meta; - const systemRole = session.config.systemRole; + const systemRole = session.config.systemRole; - return [ - meta.title || systemRole || '默认角色', - systemRole, - sessionSelectors.getAgentAvatar(meta), - meta.backgroundColor, - session?.updateAt, - s.switchSession, - s.removeSession, - ]; - }, shallow); + return [ + meta.title || systemRole || '默认角色', + systemRole, + sessionSelectors.getAgentAvatar(meta), + meta.backgroundColor, + session?.updateAt, + s.switchSession, + s.removeSession, + ]; + }, shallow); return ( - + removeSession(id)} + overlayStyle={{ width: 280 }} + placement={'right'} + title={'即将删除该会话,删除后该将无法找回,请确认你的操作。'} > + } classNames={{ time: styles.time }} - avatar={} + date={updateAt} + description={simple ? undefined : systemRole} + loading={loading} onClick={() => { switchAgent(id); }} @@ -95,6 +103,7 @@ const SessionItem: FC = memo(({ id, active, simple = true, loa alignItems: 'center', color: theme.colorText, }} + title={title} /> ); diff --git a/src/pages/chat/SessionList/List/index.tsx b/src/pages/chat/SessionList/List/index.tsx index ed1bf3e01fcc..58d60c8a91a0 100644 --- a/src/pages/chat/SessionList/List/index.tsx +++ b/src/pages/chat/SessionList/List/index.tsx @@ -19,7 +19,7 @@ export const useStyles = createStyles(({ css, token }) => ({ justify-content: center; margin-top: 8px; - padding: 12px 12px; + padding: 12px; background: ${rgba(token.colorBgLayout, 0.5)}; backdrop-filter: blur(8px); @@ -35,7 +35,13 @@ const SessionList = memo(() => { return ( <> {list.map(({ id }) => ( - + ))} ); diff --git a/src/pages/chat/SessionList/index.tsx b/src/pages/chat/SessionList/index.tsx index 38b7667bfe42..f00dd9a2ad1b 100644 --- a/src/pages/chat/SessionList/index.tsx +++ b/src/pages/chat/SessionList/index.tsx @@ -3,6 +3,7 @@ import { memo } from 'react'; import { Flexbox } from 'react-layout-kit'; import FolderPanel from '@/features/FolderPanel'; + import Header from './Header'; import SessionList from './List'; diff --git a/src/pages/chat/Sidebar.tsx b/src/pages/chat/Sidebar.tsx index c2b0b3734385..f9f5c3a35fe3 100644 --- a/src/pages/chat/Sidebar.tsx +++ b/src/pages/chat/Sidebar.tsx @@ -1,22 +1,33 @@ -import { useSettings } from '@/store/settings'; import { ActionIcon, Logo, SideNav } from '@lobehub/ui'; import { Album, MessageSquare, Settings2 } from 'lucide-react'; import { memo } from 'react'; import { shallow } from 'zustand/shallow'; +import { useSettings } from '@/store/settings'; + const Sidebar = memo(() => { const [tab, setTab] = useSettings((s) => [s.sidebarKey, s.switchSideBar], shallow); return ( } + bottomActions={} style={{ height: '100vh' }} topActions={ <> - setTab('chat')} /> - setTab('market')} /> + setTab('chat')} + size="large" + /> + setTab('market')} + size="large" + /> } - bottomActions={} /> ); }); diff --git a/src/pages/chat/[id].page.tsx b/src/pages/chat/[id].page.tsx index d28127c1e822..d3f5c78e5929 100644 --- a/src/pages/chat/[id].page.tsx +++ b/src/pages/chat/[id].page.tsx @@ -38,12 +38,15 @@ const ChatLayout = () => { {title ? `${title} - LobeChat` : 'LobeChat'} - +
- + diff --git a/src/pages/chat/index.page.tsx b/src/pages/chat/index.page.tsx index 00f2c8ea583b..27ff4c12726f 100644 --- a/src/pages/chat/index.page.tsx +++ b/src/pages/chat/index.page.tsx @@ -1,5 +1 @@ - - - - -export {default} from './[id].page'; \ No newline at end of file +export { default } from './[id].page'; diff --git a/src/pages/index.page.tsx b/src/pages/index.page.tsx index b1b1e01b9816..ac9e2586cd1e 100644 --- a/src/pages/index.page.tsx +++ b/src/pages/index.page.tsx @@ -1,5 +1 @@ - - - - -export {default} from './chat/index.page'; \ No newline at end of file +export { default } from './chat/index.page'; diff --git a/src/prompts/agent.ts b/src/prompts/agent.ts index 2373a2c45387..575f51b23d64 100644 --- a/src/prompts/agent.ts +++ b/src/prompts/agent.ts @@ -4,30 +4,30 @@ import { OpenAIStreamPayload } from '@/pages/api/OpenAIStream'; export const promptSummaryAgentName = (content: string): Partial => ({ messages: [ { - role: 'system', content: `你是一名擅长起名的起名大师,你需要将用户的描述总结为 20 个字以内的角色,格式要求如下: 输入: {文本作为JSON引用字符串} 输出: {角色名} `, + role: 'system', }, { - role: 'user', content: `输入: {你是一名专业的前端开发者,擅长结合 vitest 和\`testing-library/react\` 书写单元测试。接下来用户会输入一串 ts 代码,你需要给出完善的单元测试。\n你需要注意,单元测试代码中,不应该使用 jest 。如果需要使用 \`jest.fn\`,请使用 \`vi.fn\` 替换}`, + role: 'user', }, - { role: 'assistant', content: '前端 vitest 测试专家' }, + { content: '前端 vitest 测试专家', role: 'assistant' }, { - role: 'user', content: `输入: {你是一名前端专家,请将下面的代码转成 ts,不要修改实现。如果原本 js 中没有定义的全局变量,需要补充 declare 的类型声明。}`, + role: 'user', }, - { role: 'assistant', content: 'js 转 ts 专家' }, + { content: 'js 转 ts 专家', role: 'assistant' }, { - role: 'user', content: `输入:{你是一名擅长比喻和隐喻的UX Writter。用户会输入文案,你需要给出3个优化后的结果,使用 markdown格式的文本。下面是一个例子: 输入:页面加载中 输出:页面似乎在思考,一会儿才能准备好}`, + role: 'user', }, - { role: 'assistant', content: '文案比喻优化专家' }, - { role: 'user', content: `输入: {${content}}` }, + { content: '文案比喻优化专家', role: 'assistant' }, + { content: `输入: {${content}}`, role: 'user' }, ], }); @@ -35,7 +35,6 @@ export const promptSummaryAgentName = (content: string): Partial => ({ messages: [ { - role: 'system', content: `你是一名非常懂设计与时尚的设计师,你需要从用户的描述中匹配一个合适的 emoji。 输入:你是一名精通体验设计的设计系统设计师,设计系统存在诸多类别的 token,比如品牌色、成功色等,你需要为各个类别的 token 提供说明文案。 输出: 💅 @@ -43,10 +42,11 @@ export const promptPickEmoji = (content: string): Partial = 输入:用户会输入一串 ts 代码,为了确保所有功能和分支的 100% 的覆盖率,你需要给出需要考虑哪些数据场景。 输出: 🧪 `, + role: 'system', }, { - role: 'user', content: `输入:${content}`, + role: 'user', }, ], }); diff --git a/src/prompts/chat.ts b/src/prompts/chat.ts index 99279fd72fa0..8aa5721e8b2e 100644 --- a/src/prompts/chat.ts +++ b/src/prompts/chat.ts @@ -1,29 +1,37 @@ import { OpenAIStreamPayload } from '@/pages/api/OpenAIStream'; import { OpenAIChatMessage } from '@/types/openai'; -export const promptSummaryTitle = (messages: OpenAIChatMessage[]): Partial => ({ +export const promptSummaryTitle = ( + messages: OpenAIChatMessage[], +): Partial => ({ messages: [ { + content: + '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不需要包含标点符号', role: 'system', - content: '你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,不需要包含标点符号', }, { - role: 'user', content: `${messages.map((message) => `${message.role}: ${message.content}`).join('\n')} 请总结上述对话为10个字以内的标题,不需要包含标点符号`, + role: 'user', }, ], }); -export const promptSummaryDescription = (messages: OpenAIChatMessage[]): Partial => ({ +export const promptSummaryDescription = ( + messages: OpenAIChatMessage[], +): Partial => ({ messages: [ - { role: 'system', content: '你是一名擅长会话的助理,你需要将用户的会话做一个3句话以内的总结。' }, { - role: 'user', + content: '你是一名擅长会话的助理,你需要将用户的会话做一个3句话以内的总结。', + role: 'system', + }, + { content: `${messages.map((message) => `${message.role}: ${message.content}`).join('\n')} 请总结上述对话内容,不超过 50 个字。`, + role: 'user', }, ], }); diff --git a/src/services/chatModel.ts b/src/services/chatModel.ts index d5d173082bcb..4a54cf805459 100644 --- a/src/services/chatModel.ts +++ b/src/services/chatModel.ts @@ -1,6 +1,7 @@ -import type { OpenAIStreamPayload } from '@/pages/api/OpenAIStream'; import { merge } from 'lodash-es'; +import type { OpenAIStreamPayload } from '@/pages/api/OpenAIStream'; + import { URLS } from './url'; /** @@ -13,18 +14,18 @@ export const fetchChatModel = ( const payload = merge( { model: 'gpt-3.5-turbo', - temperature: 0.6, stream: true, + temperature: 0.6, }, params, ); return fetch(URLS.openai, { - method: 'POST', + body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(payload), + method: 'POST', signal, }); }; diff --git a/src/services/langChain.ts b/src/services/langChain.ts index 68befd6c9324..fdc0f379fccd 100644 --- a/src/services/langChain.ts +++ b/src/services/langChain.ts @@ -8,11 +8,11 @@ import { fetchAIFactory } from '@/utils/fetch'; export const fetchLangChain = fetchAIFactory( (params: LangChainParams, signal?: AbortSignal | undefined) => fetch(URLS.chain, { - method: 'POST', + body: JSON.stringify(params), headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(params), + method: 'POST', signal, }), ); diff --git a/src/services/url.ts b/src/services/url.ts index bb13a2faf110..0bcc7de46cf6 100644 --- a/src/services/url.ts +++ b/src/services/url.ts @@ -3,6 +3,6 @@ const isDev = process.env.NODE_ENV === 'development'; const prefix = isDev ? '-dev' : ''; export const URLS = { - openai: '/api/openai' + prefix, chain: '/api/chain' + prefix, + openai: '/api/openai' + prefix, }; diff --git a/src/store/session/slices/agentConfig/selectors.ts b/src/store/session/slices/agentConfig/selectors.ts index 02c4ab20c287..99213d7f2156 100644 --- a/src/store/session/slices/agentConfig/selectors.ts +++ b/src/store/session/slices/agentConfig/selectors.ts @@ -1,6 +1,6 @@ import { SessionStore } from '@/store/session'; - import { LanguageModel } from '@/types/llm'; + import { sessionSelectors } from '../session'; const currentAgentTitle = (s: SessionStore) => { @@ -31,8 +31,8 @@ const currentAgentModel = (s: SessionStore): LanguageModel => { }; export const agentSelectors = { - currentAgentConfig, currentAgentAvatar, + currentAgentConfig, currentAgentModel, currentAgentTitle, }; diff --git a/src/store/session/slices/chat/action.ts b/src/store/session/slices/chat/action.ts index 2e38544533d8..b4dec6d97d07 100644 --- a/src/store/session/slices/chat/action.ts +++ b/src/store/session/slices/chat/action.ts @@ -12,6 +12,8 @@ const LOADING_FLAT = '...'; export interface ChatAction { clearMessage: () => void; + deleteMessage: (id: string) => void; + /** * @title 派发消息 * @param payload - 消息分发 @@ -20,36 +22,40 @@ export interface ChatAction { dispatchMessage: (payload: MessageDispatch) => void; generateMessage: (messages: ChatMessage[], options: FetchSSEOptions) => Promise; - /** * @title 处理消息编辑 * @param index - 消息索引或空 * @returns void */ handleMessageEditing: (messageId: string | undefined) => void; + /** * @title 重发消息 * @param index - 消息索引 * @returns Promise */ resendMessage: (id: string) => Promise; - /** * @title 发送消息 * @returns Promise */ sendMessage: (text: string) => Promise; - deleteMessage: (id: string) => void; } -export const createChatSlice: StateCreator = ( - set, - get, -) => ({ +export const createChatSlice: StateCreator< + SessionStore, + [['zustand/devtools', never]], + [], + ChatAction +> = (set, get) => ({ clearMessage: () => { get().dispatchMessage({ type: 'resetMessages' }); }, + deleteMessage: (id) => { + get().dispatchMessage({ id, type: 'deleteMessage' }); + }, + dispatchMessage: (payload) => { const { activeId } = get(); const session = sessionSelectors.currentSession(get()); @@ -183,8 +189,4 @@ export const createChatSlice: StateCreator { - get().dispatchMessage({ type: 'deleteMessage', id }); - }, }); diff --git a/src/store/session/slices/chat/initialState.ts b/src/store/session/slices/chat/initialState.ts index 5cc47db685ff..1f4ded46269e 100644 --- a/src/store/session/slices/chat/initialState.ts +++ b/src/store/session/slices/chat/initialState.ts @@ -1,6 +1,6 @@ export interface ChatState { - editingMessageId?: string; chatLoading: boolean; + editingMessageId?: string; } export const initialChatState: ChatState = { diff --git a/src/store/session/slices/chat/messageReducer.ts b/src/store/session/slices/chat/messageReducer.ts index 16cc7c840b9c..c3ba688756ea 100644 --- a/src/store/session/slices/chat/messageReducer.ts +++ b/src/store/session/slices/chat/messageReducer.ts @@ -6,18 +6,18 @@ import { MetaData } from '@/types/meta'; import { nanoid } from '@/utils/uuid'; interface AddMessage { - type: 'addMessage'; - message: string; - role: LLMRoleType; id?: string; - quotaId?: string; - parentId?: string; + message: string; meta?: MetaData; + parentId?: string; + quotaId?: string; + role: LLMRoleType; + type: 'addMessage'; } interface DeleteMessage { - type: 'deleteMessage'; id: string; + type: 'deleteMessage'; } interface ResetMessages { @@ -25,38 +25,43 @@ interface ResetMessages { } interface UpdateMessage { - type: 'updateMessage'; id: string; key: keyof ChatMessage; + type: 'updateMessage'; value: ChatMessage[keyof ChatMessage]; } export type MessageDispatch = AddMessage | DeleteMessage | ResetMessages | UpdateMessage; -export const messagesReducer = (state: ChatMessageMap, payload: MessageDispatch): ChatMessageMap => { +export const messagesReducer = ( + state: ChatMessageMap, + payload: MessageDispatch, +): ChatMessageMap => { switch (payload.type) { - case 'addMessage': + case 'addMessage': { return produce(state, (draftState) => { const mid = payload.id || nanoid(); draftState[mid] = { - id: mid, - role: payload.role, content: payload.message, + createAt: Date.now(), + id: mid, meta: payload.meta || {}, - quotaId: payload.quotaId, parentId: payload.parentId, + quotaId: payload.quotaId, + role: payload.role, updateAt: Date.now(), - createAt: Date.now(), }; }); + } - case 'deleteMessage': + case 'deleteMessage': { return produce(state, (draftState) => { delete draftState[payload.id]; }); + } - case 'updateMessage': + case 'updateMessage': { return produce(state, (draftState) => { const { id, key, value } = payload; const message = draftState[id]; @@ -66,11 +71,14 @@ export const messagesReducer = (state: ChatMessageMap, payload: MessageDispatch) message[key] = value; message.updateAt = Date.now(); }); + } - case 'resetMessages': + case 'resetMessages': { return {}; + } - default: + default: { throw new Error('暂未实现的 type,请检查 reducer'); + } } }; diff --git a/src/store/session/slices/chat/selectors.ts b/src/store/session/slices/chat/selectors.ts index b1fdc851ff76..948f86a8e8dd 100644 --- a/src/store/session/slices/chat/selectors.ts +++ b/src/store/session/slices/chat/selectors.ts @@ -1,6 +1,7 @@ -import { ChatMessage } from '@/types/chatMessage'; import { encode } from 'gpt-tokenizer'; +import { ChatMessage } from '@/types/chatMessage'; + import type { SessionStore } from '../../store'; import { sessionSelectors } from '../session'; @@ -40,8 +41,8 @@ const systemRoleTokenCount = (s: SessionStore) => systemRoleTokens(s).length; export const chatSelectors = { currentChats: currentChatsSel, systemRole: systemRoleSel, - totalTokens, - totalTokenCount, - systemRoleTokens, systemRoleTokenCount, + systemRoleTokens, + totalTokenCount, + totalTokens, }; diff --git a/src/store/session/slices/session/action.ts b/src/store/session/slices/session/action.ts index 5081a14972ba..12dd3b238ffc 100644 --- a/src/store/session/slices/session/action.ts +++ b/src/store/session/slices/session/action.ts @@ -15,6 +15,12 @@ export interface SessionAction { * @returns void */ createSession: () => Promise; + /** + * 分发聊天记录 + * @param payload - 聊天记录 + */ + dispatchSession: (payload: SessionDispatch) => void; + /** * @title 删除会话 * @param index - 会话索引 @@ -29,12 +35,6 @@ export interface SessionAction { */ switchSession: (sessionId?: string | 'new') => void; - /** - * 分发聊天记录 - * @param payload - 聊天记录 - */ - dispatchSession: (payload: SessionDispatch) => void; - /** * 生成压缩后的消息 * @returns 压缩后的消息 @@ -42,48 +42,50 @@ export interface SessionAction { // genShareUrl: () => string; } -export const createSessionSlice: StateCreator = ( - set, - get, -) => ({ - dispatchSession: (payload) => { - const { type, ...res } = payload; - set({ sessions: sessionsReducer(get().sessions, payload) }, false, { - type: `dispatchChat/${type}`, - payload: res, - }); - }, - +export const createSessionSlice: StateCreator< + SessionStore, + [['zustand/devtools', never]], + [], + SessionAction +> = (set, get) => ({ createSession: async () => { const { dispatchSession, switchSession } = get(); const timestamp = Date.now(); const newSession: LobeAgentSession = { - id: uuid(), - createAt: timestamp, - updateAt: timestamp, - type: LobeSessionType.Agent, chats: {}, - meta: { - title: '默认对话', - }, config: { model: LanguageModel.GPT3_5, - systemRole: '', params: { temperature: 0.6, }, + systemRole: '', + }, + createAt: timestamp, + id: uuid(), + meta: { + title: '默认对话', }, + type: LobeSessionType.Agent, + updateAt: timestamp, }; - dispatchSession({ type: 'addSession', session: newSession }); + dispatchSession({ session: newSession, type: 'addSession' }); switchSession(newSession.id); }, + dispatchSession: (payload) => { + const { type, ...res } = payload; + set({ sessions: sessionsReducer(get().sessions, payload) }, false, { + payload: res, + type: `dispatchChat/${type}`, + }); + }, + removeSession: (sessionId) => { - get().dispatchSession({ type: 'removeSession', id: sessionId }); + get().dispatchSession({ id: sessionId, type: 'removeSession' }); Router.push('/'); }, diff --git a/src/store/session/slices/session/reducers/session.ts b/src/store/session/slices/session/reducers/session.ts index 6c4d6b94999d..a53557013011 100644 --- a/src/store/session/slices/session/reducers/session.ts +++ b/src/store/session/slices/session/reducers/session.ts @@ -1,52 +1,53 @@ +import { produce } from 'immer'; + import { ChatMessageMap } from '@/types/chatMessage'; import { MetaData } from '@/types/meta'; import { LobeAgentConfig, LobeAgentSession, LobeSessions } from '@/types/session'; -import { produce } from 'immer'; /** * @title 添加会话 */ interface AddSession { + /** + * @param session - 会话信息 + */ + session: LobeAgentSession; /** * @param type - 操作类型 * @default 'addChat' */ type: 'addSession'; - /** - * @param session - 会话信息 - */ - session: LobeAgentSession; } interface RemoveSession { - type: 'removeSession'; id: string; + type: 'removeSession'; } /** * @title 更新会话聊天上下文 */ interface UpdateSessionChat { - type: 'updateSessionChat'; + chats: ChatMessageMap; /** * 会话 ID */ id: string; - chats: ChatMessageMap; + type: 'updateSessionChat'; } interface UpdateSessionMeta { - type: 'updateSessionMeta'; id: string; key: keyof MetaData; + type: 'updateSessionMeta'; value: any; } interface UpdateSessionAgentConfig { - type: 'updateSessionConfig'; - id: string; config: Partial; + id: string; + type: 'updateSessionConfig'; } export type SessionDispatch = @@ -58,17 +59,19 @@ export type SessionDispatch = export const sessionsReducer = (state: LobeSessions, payload: SessionDispatch): LobeSessions => { switch (payload.type) { - case 'addSession': + case 'addSession': { return produce(state, (draft) => { draft[payload.session.id] = payload.session; }); + } - case 'removeSession': + case 'removeSession': { return produce(state, (draft) => { delete draft[payload.id]; }); + } - case 'updateSessionMeta': + case 'updateSessionMeta': { return produce(state, (draft) => { const chat = draft[payload.id]; if (!chat) return; @@ -77,16 +80,18 @@ export const sessionsReducer = (state: LobeSessions, payload: SessionDispatch): chat.meta[key] = value; }); + } - case 'updateSessionChat': + case 'updateSessionChat': { return produce(state, (draft) => { const chat = draft[payload.id]; if (!chat) return; chat.chats = payload.chats; }); + } - case 'updateSessionConfig': + case 'updateSessionConfig': { return produce(state, (draft) => { const { id, config } = payload; const chat = draft[id]; @@ -94,8 +99,10 @@ export const sessionsReducer = (state: LobeSessions, payload: SessionDispatch): chat.config = { ...chat.config, ...config }; }); + } - default: + default: { return produce(state, () => {}); + } } }; diff --git a/src/store/session/slices/session/selectors/index.ts b/src/store/session/slices/session/selectors/index.ts index eeec4b8e6484..e9a885573c9e 100644 --- a/src/store/session/slices/session/selectors/index.ts +++ b/src/store/session/slices/session/selectors/index.ts @@ -1,12 +1,20 @@ import { getAgentAvatar } from './chat'; -import { chatListSel, currentSessionSafe, currentSessionSel, getSessionById, getSessionMetaById } from './list'; +import { + chatListSel, + currentSessionSafe, + currentSessionSel, + getSessionById, + getSessionMetaById, +} from './list'; export const sessionSelectors = { + chatList: chatListSel, currentSession: currentSessionSel, currentSessionSafe, - chatList: chatListSel, + + getAgentAvatar, + + getSessionById, // sessionTree: sessionTreeSel, getSessionMetaById, - getSessionById, - getAgentAvatar, }; diff --git a/src/store/session/slices/session/selectors/list.ts b/src/store/session/slices/session/selectors/list.ts index 5925541aefbf..68d957216248 100644 --- a/src/store/session/slices/session/selectors/list.ts +++ b/src/store/session/slices/session/selectors/list.ts @@ -1,7 +1,6 @@ import { SessionStore } from '@/store/session'; -import { LobeAgentSession } from '@/types/session'; - import { MetaData } from '@/types/meta'; +import { LobeAgentSession } from '@/types/session'; import { filterWithKeywords } from '@/utils/filter'; import { initLobeSession } from '../initialState'; diff --git a/src/store/settings.ts b/src/store/settings.ts index 94679bdf6361..8316684cf454 100644 --- a/src/store/settings.ts +++ b/src/store/settings.ts @@ -7,26 +7,26 @@ import { ConfigSettings } from '@/types/exportConfig'; export type SidebarTabKey = 'chat' | 'market'; interface SettingsStore { - sessionsWidth: number; - inputHeight: number; avatar?: string; + importSettings: (settings: ConfigSettings) => void; + inputHeight: number; sessionExpandable?: boolean; + sessionsWidth: number; sidebarKey: SidebarTabKey; - themeMode?: ThemeMode; - importSettings: (settings: ConfigSettings) => void; switchSideBar: (key: SidebarTabKey) => void; + themeMode?: ThemeMode; } export const useSettings = create()( persist( (set) => ({ - sessionsWidth: 320, - inputHeight: 200, - sessionExpandable: true, - sidebarKey: 'chat', importSettings: (settings) => { set({ ...settings }); }, + inputHeight: 200, + sessionExpandable: true, + sessionsWidth: 320, + sidebarKey: 'chat', switchSideBar: (key) => { set({ sidebarKey: key }); }, diff --git a/src/types/chatMessage.ts b/src/types/chatMessage.ts index 49883dbe1c70..a19de5eec6ed 100644 --- a/src/types/chatMessage.ts +++ b/src/types/chatMessage.ts @@ -14,11 +14,7 @@ export interface ChatMessageError { } export interface ChatMessage extends BaseDataModel { - /** - * 角色 - * @description 消息发送者的角色 - */ - role: LLMRoleType; + archive?: boolean; /** * @title 内容 @@ -27,20 +23,24 @@ export interface ChatMessage extends BaseDataModel { content: string; error?: any; - archive?: boolean; - - parentId?: string; - // 引用 - quotaId?: string; // 扩展字段 extra?: { // 翻译 translate: { - to: string; target: string; + to: string; }; // 语音 } & Record; + + parentId?: string; + // 引用 + quotaId?: string; + /** + * 角色 + * @description 消息发送者的角色 + */ + role: LLMRoleType; } export type ChatMessageMap = Record; diff --git a/src/types/langchain.ts b/src/types/langchain.ts index 95aa5c5a5090..2f29bdf44111 100644 --- a/src/types/langchain.ts +++ b/src/types/langchain.ts @@ -2,28 +2,28 @@ import { ChatMessage } from '@lobehub/ui'; export interface LangChainParams { llm: { - model: string; - /** - * 生成文本的随机度量,用于控制文本的创造性和多样性 - * @default 0.6 - */ - temperature: number; - /** - * 控制生成文本中最高概率的单个令牌 - */ - top_p?: number; /** * 控制生成文本中的惩罚系数,用于减少重复性 */ frequency_penalty?: number; + /** + * 生成文本的最大长度 + */ + max_tokens?: number; + model: string; /** * 控制生成文本中的惩罚系数,用于减少主题的变化 */ presence_penalty?: number; /** - * 生成文本的最大长度 + * 生成文本的随机度量,用于控制文本的创造性和多样性 + * @default 0.6 */ - max_tokens?: number; + temperature: number; + /** + * 控制生成文本中最高概率的单个令牌 + */ + top_p?: number; }; /** diff --git a/src/types/meta.ts b/src/types/meta.ts index dc4634be01ab..77abe8b173fd 100644 --- a/src/types/meta.ts +++ b/src/types/meta.ts @@ -1,11 +1,4 @@ export interface MetaData { - /** - * 名称 - * @description 可选参数,如果不传则使用默认名称 - */ - title?: string; - description?: string; - tag?: string[]; /** * 角色头像 * @description 可选参数,如果不传则使用默认头像 @@ -16,11 +9,18 @@ export interface MetaData { * @description 可选参数,如果不传则使用默认背景色 */ backgroundColor?: string; + description?: string; + tag?: string[]; + /** + * 名称 + * @description 可选参数,如果不传则使用默认名称 + */ + title?: string; } export interface BaseDataModel { + createAt: number; id: string; meta: MetaData; updateAt: number; - createAt: number; } diff --git a/src/types/session.ts b/src/types/session.ts index b0e8d665858e..4f9b53676208 100644 --- a/src/types/session.ts +++ b/src/types/session.ts @@ -15,17 +15,21 @@ export enum LobeSessionType { interface LobeSessionBase extends BaseDataModel { /** - * 每个会话的类别 + * 聊天记录 */ - type: LobeSessionType; + chats: ChatMessageMap; /** - * 聊天记录 + * 每个会话的类别 */ - chats: ChatMessageMap; + type: LobeSessionType; } export interface LobeAgentConfig { + /** + * 语言模型示例 + */ + example?: LLMExample; /** * 角色所使用的语言模型 * @default gpt-3.5-turbo @@ -39,21 +43,17 @@ export interface LobeAgentConfig { * 系统角色 */ systemRole: string; - /** - * 语言模型示例 - */ - example?: LLMExample; } /** * Lobe Agent会话 */ export interface LobeAgentSession extends LobeSessionBase { - type: LobeSessionType.Agent; /** * 语言模型角色设定 */ config: LobeAgentConfig; + type: LobeSessionType.Agent; } export type LobeSessions = Record; diff --git a/src/utils/VersionController.ts b/src/utils/VersionController.ts index 43c1791b9d17..ff1395b3f179 100644 --- a/src/utils/VersionController.ts +++ b/src/utils/VersionController.ts @@ -3,16 +3,16 @@ * @template T - 状态类型 */ export interface Migration { - /** - * 迁移版本号 - */ - version: number; /** * 迁移数据 * @param data - 迁移数据 * @returns 迁移后的数据 */ migrate(data: MigrationData): MigrationData; + /** + * 迁移版本号 + */ + version: number; } /** diff --git a/src/utils/compass.ts b/src/utils/compass.ts index 9bbf877646fe..6b3ad6778673 100644 --- a/src/utils/compass.ts +++ b/src/utils/compass.ts @@ -8,8 +8,8 @@ export class StrCompressor { * @ignore */ private instance!: { - decompress(buf: Uint8Array): Uint8Array; compress(buf: Uint8Array, options?: any): Uint8Array; + decompress(buf: Uint8Array): Uint8Array; }; async init(): Promise { diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts index 8968752eaf2c..15dd9e7f479d 100644 --- a/src/utils/fetch.ts +++ b/src/utils/fetch.ts @@ -21,8 +21,8 @@ const codeMessage: Record = { }; export interface FetchSSEOptions { - onMessageHandle?: (text: string) => void; onErrorHandle?: (error: ChatMessageError) => void; + onMessageHandle?: (text: string) => void; } /** @@ -68,15 +68,7 @@ export const fetchSSE = async (fetchFn: () => Promise, options: FetchS }; interface FetchAITaskResultParams { - /** - * 请求对象 - */ - params: T; - /** - * 消息处理函数 - * @param text - 消息内容 - */ - onMessageHandle?: (text: string) => void; + abortController?: AbortController; /** * 错误处理函数 */ @@ -86,13 +78,27 @@ interface FetchAITaskResultParams { * @param loading - 是否处于加载状态 */ onLoadingChange?: (loading: boolean) => void; + /** + * 消息处理函数 + * @param text - 消息内容 + */ + onMessageHandle?: (text: string) => void; - abortController?: AbortController; + /** + * 请求对象 + */ + params: T; } export const fetchAIFactory = (fetcher: (params: T, signal?: AbortSignal) => Promise) => - async ({ params, onMessageHandle, onError, onLoadingChange, abortController }: FetchAITaskResultParams) => { + async ({ + params, + onMessageHandle, + onError, + onLoadingChange, + abortController, + }: FetchAITaskResultParams) => { const errorHandle = (error: Error) => { onLoadingChange?.(false); if (abortController?.signal.aborted) { @@ -112,10 +118,10 @@ export const fetchAIFactory = onLoadingChange?.(true); const data = await fetchSSE(() => fetcher(params, abortController?.signal), { - onMessageHandle, onErrorHandle: (error) => { errorHandle(new Error(error.message)); }, + onMessageHandle, }).catch(errorHandle); onLoadingChange?.(false); diff --git a/src/utils/uuid.ts b/src/utils/uuid.ts index a25793ff90b7..3c9d63e6cd59 100644 --- a/src/utils/uuid.ts +++ b/src/utils/uuid.ts @@ -1,8 +1,4 @@ - - - // generate('1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 16); //=> "4f90d13a42" - import { customAlphabet } from 'nanoid/non-secure'; export const nanoid = customAlphabet( @@ -10,4 +6,4 @@ export const nanoid = customAlphabet( 8, ); -export {v4 as uuid} from 'uuid'; \ No newline at end of file +export { v4 as uuid } from 'uuid'; diff --git a/tsconfig.json b/tsconfig.json index a5a8108b21cd..3ccd653b687f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,6 @@ "@/*": ["src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "exclude": ["node_modules"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] }