From 4e522a6e2b587d2cf40d8dd59eb4409ceb8f24b8 Mon Sep 17 00:00:00 2001 From: canisminor1990 Date: Sat, 15 Jul 2023 15:48:20 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20chore:=20Update=20setting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces new components, modules, and features related to chat, sessions, and settings. It includes modifications to configuration files, updates to dependencies, adjustments to styles and layouts, and additions of new components and modules. The changes also involve updates to functions, interfaces, selectors, actions, and reducers. Additionally, there are modifications to TypeScript interfaces and types, as well as changes to parameters and functions in certain files. --- .changelogrc.js | 1 + .commitlintrc.js | 1 + .editorconfig | 16 ++++ .eslintignore | 32 ++++++++ .eslintrc.js | 11 +-- .github/ISSUE_TEMPLATE/1_bug_report.yml | 20 ++--- .github/ISSUE_TEMPLATE/2_feature_request.yml | 14 ++-- .github/ISSUE_TEMPLATE/3_question.yml | 12 +-- .github/dependabot.yml | 10 +-- .github/workflows/contributor-help.yml | 2 +- .github/workflows/issue-check-inactive.yml | 8 +- .github/workflows/issue-close-require.yml | 6 +- .github/workflows/issue-remove-inactive.yml | 6 +- .gitignore | 1 - .gitpod.yml | 3 + .npmrc | 9 +++ .prettierignore | 79 +++++++++++++------ .prettierrc.js | 11 +-- .releaserc.js | 5 +- .remarkrc.js | 1 + .stylelintrc.js | 10 +-- README.md | 2 +- package.json | 8 +- src/features/FolderPanel/index.tsx | 27 ++++--- src/helpers/prompt.test.ts | 3 +- src/helpers/prompt.ts | 9 ++- src/helpers/url.ts | 7 +- src/layout/index.tsx | 3 +- src/layout/style.ts | 2 +- src/pages/_app.page.tsx | 3 +- src/pages/_document.page.tsx | 2 +- src/pages/api/LangChainStream.ts | 23 +++--- src/pages/api/OpenAIStream.ts | 57 +++++++------ src/pages/chat/Config/ConfigCell.tsx | 17 ++-- src/pages/chat/Config/ReadMode.tsx | 17 ++-- src/pages/chat/Config/index.tsx | 28 ++++--- src/pages/chat/Conversation/ChatList.tsx | 11 ++- src/pages/chat/Conversation/Input.tsx | 11 ++- src/pages/chat/Conversation/index.tsx | 4 +- src/pages/chat/Header.tsx | 23 +++--- src/pages/chat/SessionList/Header.tsx | 19 +++-- .../chat/SessionList/List/SessionItem.tsx | 67 +++++++++------- src/pages/chat/SessionList/List/index.tsx | 10 ++- src/pages/chat/SessionList/index.tsx | 1 + src/pages/chat/Sidebar.tsx | 19 ++++- src/pages/chat/[id].page.tsx | 7 +- src/pages/chat/index.page.tsx | 6 +- src/pages/index.page.tsx | 6 +- src/prompts/agent.ts | 20 ++--- src/prompts/chat.ts | 20 +++-- src/services/chatModel.ts | 9 ++- src/services/langChain.ts | 4 +- src/services/url.ts | 2 +- .../session/slices/agentConfig/selectors.ts | 4 +- src/store/session/slices/chat/action.ts | 24 +++--- src/store/session/slices/chat/initialState.ts | 2 +- .../session/slices/chat/messageReducer.ts | 42 ++++++---- src/store/session/slices/chat/selectors.ts | 9 ++- src/store/session/slices/session/action.ts | 58 +++++++------- .../slices/session/reducers/session.ts | 41 ++++++---- .../session/slices/session/selectors/index.ts | 16 +++- .../session/slices/session/selectors/list.ts | 3 +- src/store/settings.ts | 16 ++-- src/types/chatMessage.ts | 22 +++--- src/types/langchain.ts | 24 +++--- src/types/meta.ts | 16 ++-- src/types/session.ts | 18 ++--- src/utils/VersionController.ts | 8 +- src/utils/compass.ts | 2 +- src/utils/fetch.ts | 32 +++++--- src/utils/uuid.ts | 6 +- tsconfig.json | 4 +- 72 files changed, 618 insertions(+), 434 deletions(-) create mode 100644 .changelogrc.js create mode 100644 .commitlintrc.js create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .gitpod.yml create mode 100644 .remarkrc.js diff --git a/.changelogrc.js b/.changelogrc.js new file mode 100644 index 0000000000000..9a2f5f98d8cd6 --- /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 0000000000000..9b8c6ace5a004 --- /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 0000000000000..7e3649acc2c16 --- /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 0000000000000..b245203304656 --- /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 b2d8f1a1ff46e..920af1d9c0926 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 c8aa08e70d1ed..d181c38798158 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 b1a9d1e219d39..edcf7d0643cc3 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 687666d0f03f8..f989f7d11be92 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 5702943a41f7a..1f8319428a3ae 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 9930606609164..fd68fec3bd42b 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 2621364ba55ab..d37c4c3071973 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 1b92397901925..68d6b6cefe850 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 3b25e9d0eb522..dbe42ddb86d23 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 4577ee4f28430..0d17fd564a7cb 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 0000000000000..e605228076a13 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,3 @@ +tasks: + - init: pnpm install + command: pnpm run start diff --git a/.npmrc b/.npmrc index 89cfc9f9cf106..d9ed3d371eb9d 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 2bfa66c3a0be1..3e459cbe47962 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 8be3baf87a51f..f0355a9c1a75f 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 7b278591ede9a..37930011d9d43 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 0000000000000..b673c10ed1cc7 --- /dev/null +++ b/.remarkrc.js @@ -0,0 +1 @@ +module.exports = require('@lobehub/lint').remarklint; diff --git a/.stylelintrc.js b/.stylelintrc.js index bbd3844f4c25c..c40700dd91da8 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 c07c25a46faca..57078fd04bdd6 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 112d76c0b56f1..7f5399f1b79d1 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 d9f9c31e580d6..9bd4c71811414 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 ee180dcc7ec36..10a6036883e60 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 ce72da1f4c2b2..9719d7bef21b2 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 f01e9d29f4a13..2b255303c884e 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 8f40938eaea4f..ea0c164c810d0 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 fc2acd35e65fe..b0e8dd5e9be1e 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 a99a22d46402d..653c73be705b5 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 09db63c5fcb2e..5b19b2d822941 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 2b14a2767a1c1..69f0c19d1afbf 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 678030de8d3ae..8704de701ac99 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 93fa2fe6a6bfc..d4c8c3c743c8e 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 55e291d47a50d..ea0823160f754 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 a9aa688f3e156..5b9cfdfca01f6 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 30b11ae37e710..c52ef7b9a3878 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 42090646205e9..c610698e648f8 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 a30a06210bc20..7efe9af0bfc30 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 ed1bf3e01fcc7..58d60c8a91a09 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 38b7667bfe421..f00dd9a2ad1b3 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 c2b0b37343855..f9f5c3a35fe3d 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 d28127c1e8228..d3f5c78e59297 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 00f2c8ea583b8..27ff4c12726f2 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 b1b1e01b98160..ac9e2586cd1e7 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 2373a2c45387a..575f51b23d64d 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 99279fd72fa0c..8aa5721e8b2eb 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 d5d173082bcbf..4a54cf805459a 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 68befd6c93241..fdc0f379fccd6 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 bb13a2faf1100..0bcc7de46cf6c 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 02c4ab20c2871..99213d7f21568 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 2e38544533d83..b4dec6d97d077 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 5cc47db685ffc..1f4ded46269e3 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 16cc7c840b9c3..c3ba688756ea6 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 b1fdc851ff76d..948f86a8e8ddb 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 5081a14972ba8..12dd3b238ffca 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 6c4d6b94999d6..a535570130114 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 eeec4b8e64849..e9a885573c9ee 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 5925541aefbfd..68d9572162484 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 94679bdf63617..8316684cf454f 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 49883dbe1c702..a19de5eec6ed0 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 95aa5c5a50900..2f29bdf44111d 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 dc4634be01ab0..77abe8b173fd8 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 b0e8d665858ef..4f9b536762083 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 43c1791b9d17c..ff1395b3f179b 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 9bbf877646fe7..6b3ad67786735 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 8968752eaf2c6..15dd9e7f479d5 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 a25793ff90b7a..3c9d63e6cd59f 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 a5a8108b21cd5..3ccd653b687f4 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"] }