@lobehub/chat 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.22.0](https://github.com/lobehub/lobe-chat/compare/v0.21.0...v0.22.0)
6
+
7
+ <sup>Released on **2023-07-25**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: 支持使用自定义 OpenAI Key.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: 支持使用自定义 OpenAI Key, closes [#20](https://github.com/lobehub/lobe-chat/issues/20) ([fb454a0](https://github.com/lobehub/lobe-chat/commit/fb454a0))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 0.21.0](https://github.com/lobehub/lobe-chat/compare/v0.20.0...v0.21.0)
31
+
32
+ <sup>Released on **2023-07-25**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Move component folder.
37
+
38
+ #### ✨ Features
39
+
40
+ - **misc**: 支持快捷配置模型、温度.
41
+
42
+ <br/>
43
+
44
+ <details>
45
+ <summary><kbd>Improvements and Fixes</kbd></summary>
46
+
47
+ #### Code refactoring
48
+
49
+ - **misc**: Move component folder ([fb85d16](https://github.com/lobehub/lobe-chat/commit/fb85d16))
50
+
51
+ #### What's improved
52
+
53
+ - **misc**: 支持快捷配置模型、温度, closes [#19](https://github.com/lobehub/lobe-chat/issues/19) ([31daee1](https://github.com/lobehub/lobe-chat/commit/31daee1))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ## [Version 0.20.0](https://github.com/lobehub/lobe-chat/compare/v0.19.0...v0.20.0)
6
64
 
7
65
  <sup>Released on **2023-07-25**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -20,6 +20,7 @@ const SliderWithInput = memo<SliderSingleProps>(
20
20
  onChange={handleOnchange}
21
21
  step={step}
22
22
  style={{ flex: 1 }}
23
+ tooltip={{ open: false }}
23
24
  value={typeof value === 'number' ? value : 0}
24
25
  {...props}
25
26
  />
@@ -1 +1,3 @@
1
1
  export const OPENAI_SERVICE_ERROR_CODE = 555;
2
+
3
+ export const OPENAI_API_KEY_HEADER_KEY = 'X-OPENAI-API-KEY';
@@ -102,6 +102,7 @@ export default {
102
102
  temperature: {
103
103
  desc: '值越大,回复越随机',
104
104
  title: '随机性',
105
+ titleWithValue: '随机性 {{value}}',
105
106
  },
106
107
  title: '模型设置',
107
108
  topP: {
@@ -1,5 +1,6 @@
1
1
  import { StreamingTextResponse } from 'ai';
2
2
 
3
+ import { OPENAI_API_KEY_HEADER_KEY } from '@/const/fetch';
3
4
  import { OpenAIStreamPayload } from '@/types/openai';
4
5
 
5
6
  import { createChatCompletion } from './openai';
@@ -8,8 +9,9 @@ export const runtime = 'edge';
8
9
 
9
10
  export default async function handler(req: Request) {
10
11
  const payload = (await req.json()) as OpenAIStreamPayload;
12
+ const apiKey = req.headers.get(OPENAI_API_KEY_HEADER_KEY);
11
13
 
12
- const stream = await createChatCompletion(payload);
14
+ const stream = await createChatCompletion({ OPENAI_API_KEY: apiKey, payload });
13
15
 
14
16
  return new StreamingTextResponse(stream);
15
17
  }
@@ -9,20 +9,30 @@ import pluginList from '../../plugins';
9
9
  const isDev = process.env.NODE_ENV === 'development';
10
10
  const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL;
11
11
 
12
- // Create an OpenAI API client (that's edge friendly!)
13
- const config = new Configuration({
14
- apiKey: process.env.OPENAI_API_KEY,
15
- });
16
-
17
- export const openai = new OpenAIApi(
18
- config,
19
- isDev && OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined,
20
- );
21
-
22
- export const createChatCompletion = async (
23
- payload: OpenAIStreamPayload,
24
- callbacks?: (payload: OpenAIStreamPayload) => OpenAIStreamCallbacks,
25
- ) => {
12
+ // 创建 OpenAI 实例
13
+ export const createOpenAI = (OPENAI_API_KEY: string | null) => {
14
+ const config = new Configuration({
15
+ apiKey: OPENAI_API_KEY ?? process.env.OPENAI_API_KEY,
16
+ });
17
+
18
+ return new OpenAIApi(config, isDev && OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined);
19
+ };
20
+
21
+ interface CreateChatCompletionOptions {
22
+ OPENAI_API_KEY: string | null;
23
+ callbacks?: (payload: OpenAIStreamPayload) => OpenAIStreamCallbacks;
24
+ payload: OpenAIStreamPayload;
25
+ }
26
+
27
+ export const createChatCompletion = async ({
28
+ payload,
29
+ callbacks,
30
+ OPENAI_API_KEY,
31
+ }: CreateChatCompletionOptions) => {
32
+ // ============ 0.创建 OpenAI 实例 ============ //
33
+
34
+ const openai = createOpenAI(OPENAI_API_KEY);
35
+
26
36
  const { messages, plugins: enabledPlugins, ...params } = payload;
27
37
 
28
38
  // ============ 1. 前置处理 functions ============ //
@@ -42,6 +52,8 @@ export const createChatCompletion = async (
42
52
  // ============ 2. 前置处理 messages ============ //
43
53
  const formatMessages = messages.map((m) => ({ content: m.content, role: m.role }));
44
54
 
55
+ // ============ 3. 发送请求 ============ //
56
+
45
57
  const requestParams = { functions, messages: formatMessages, stream: true, ...params };
46
58
 
47
59
  const response = await openai.createChatCompletion(requestParams);
@@ -1,36 +1,47 @@
1
1
  import { StreamingTextResponse } from 'ai';
2
2
  import { ChatCompletionRequestMessage } from 'openai-edge';
3
3
 
4
+ import { OPENAI_API_KEY_HEADER_KEY } from '@/const/fetch';
4
5
  import { OpenAIStreamPayload } from '@/types/openai';
5
6
 
6
7
  import pluginList from '../../plugins';
7
- import { createChatCompletion, openai } from './openai';
8
+ import { createChatCompletion, createOpenAI } from './openai';
8
9
 
9
10
  export const runtime = 'edge';
10
11
 
11
12
  export default async function handler(req: Request) {
12
13
  const payload = (await req.json()) as OpenAIStreamPayload;
14
+ const apiKey = req.headers.get(OPENAI_API_KEY_HEADER_KEY);
13
15
 
14
- const stream = await createChatCompletion(payload, (payload) => ({
15
- experimental_onFunctionCall: async ({ name, arguments: args }, createFunctionCallMessages) => {
16
- console.log(`检测到 functionCall: ${name}`);
16
+ const openai = createOpenAI(apiKey);
17
17
 
18
- const func = pluginList.find((f) => f.name === name);
18
+ const stream = await createChatCompletion({
19
+ OPENAI_API_KEY: apiKey,
20
+ callbacks: (payload) => ({
21
+ experimental_onFunctionCall: async (
22
+ { name, arguments: args },
23
+ createFunctionCallMessages,
24
+ ) => {
25
+ console.log(`检测到 functionCall: ${name}`);
19
26
 
20
- if (func) {
21
- const result = await func.runner(args as any);
27
+ const func = pluginList.find((f) => f.name === name);
22
28
 
23
- console.log(`[${name}]`, args, `result:`, JSON.stringify(result, null, 2));
29
+ if (func) {
30
+ const result = await func.runner(args as any);
24
31
 
25
- const newMessages = createFunctionCallMessages(result) as ChatCompletionRequestMessage[];
32
+ console.log(`[${name}]`, args, `result:`, JSON.stringify(result, null, 2));
26
33
 
27
- return openai.createChatCompletion({
28
- ...payload,
29
- messages: [...payload.messages, ...newMessages],
30
- });
31
- }
32
- },
33
- }));
34
+ const newMessages = createFunctionCallMessages(result) as ChatCompletionRequestMessage[];
35
+
36
+ return openai.createChatCompletion({
37
+ ...payload,
38
+ messages: [...payload.messages, ...newMessages],
39
+ });
40
+ }
41
+ },
42
+ }),
43
+ payload,
44
+ });
34
45
 
35
46
  return new StreamingTextResponse(stream);
36
47
  }
@@ -1,21 +1,80 @@
1
1
  import { ActionIcon } from '@lobehub/ui';
2
- import { Popconfirm } from 'antd';
3
- import { BrainCog, Eraser, Thermometer, Timer } from 'lucide-react';
2
+ import { Dropdown, Popconfirm, Popover } from 'antd';
3
+ import { BrainCog, Eraser, Thermometer } from 'lucide-react';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { shallow } from 'zustand/shallow';
7
7
 
8
- import { useSessionStore } from '@/store/session';
8
+ import SliderWithInput from '@/components/SliderWithInput';
9
+ import { agentSelectors, useSessionStore } from '@/store/session';
10
+ import { LanguageModel } from '@/types/llm';
9
11
 
10
12
  const InputActions = memo(() => {
11
13
  const { t } = useTranslation('setting');
12
- const [clearMessage] = useSessionStore((s) => [s.clearMessage], shallow);
14
+ const [clearMessage, updateAgentConfig] = useSessionStore(
15
+ (s) => [s.clearMessage, s.updateAgentConfig],
16
+ shallow,
17
+ );
18
+ const [model, temperature] = useSessionStore((s) => {
19
+ const config = agentSelectors.currentAgentConfigSafe(s);
20
+ return [
21
+ config.model,
22
+ config.params.temperature,
23
+ // config.historyCount
24
+ ];
25
+ }, shallow);
13
26
 
14
27
  return (
15
28
  <>
16
- <ActionIcon icon={BrainCog} title={t('settingModel.model.title')} />
17
- <ActionIcon icon={Thermometer} title={t('settingModel.temperature.title')} />
18
- <ActionIcon icon={Timer} title={t('settingChat.historyCount.title')} />
29
+ <Dropdown
30
+ menu={{
31
+ activeKey: model,
32
+ items: Object.values(LanguageModel).map((i) => ({ key: i, label: i })),
33
+ onClick: (e) => {
34
+ updateAgentConfig({ model: e.key as LanguageModel });
35
+ },
36
+ }}
37
+ trigger={['click']}
38
+ >
39
+ <ActionIcon icon={BrainCog} title={t('settingModel.model.title')} />
40
+ </Dropdown>
41
+ <Popover
42
+ content={
43
+ <SliderWithInput
44
+ max={1}
45
+ min={0}
46
+ onChange={(v) => {
47
+ updateAgentConfig({ params: { temperature: v } });
48
+ }}
49
+ step={0.1}
50
+ style={{ width: 120 }}
51
+ value={temperature}
52
+ />
53
+ }
54
+ trigger={'click'}
55
+ >
56
+ <ActionIcon
57
+ icon={Thermometer}
58
+ title={t('settingModel.temperature.titleWithValue', { value: temperature })}
59
+ />
60
+ </Popover>
61
+ {/*TODO 历史记录功能实现 */}
62
+ {/*<Popover*/}
63
+ {/* content={*/}
64
+ {/* <SliderWithInput*/}
65
+ {/* min={0}*/}
66
+ {/* onChange={(v) => {*/}
67
+ {/* updateAgentConfig({ historyCount: v });*/}
68
+ {/* }}*/}
69
+ {/* step={1}*/}
70
+ {/* style={{ width: 120 }}*/}
71
+ {/* value={historyCount}*/}
72
+ {/* />*/}
73
+ {/* }*/}
74
+ {/* trigger={'click'}*/}
75
+ {/*>*/}
76
+ {/* <ActionIcon icon={Timer} title={t('settingChat.historyCount.title')} />*/}
77
+ {/*</Popover>*/}
19
78
  <Popconfirm
20
79
  cancelText={t('cancel', { ns: 'common' })}
21
80
  okButtonProps={{ danger: true }}
@@ -6,10 +6,10 @@ import { debounce } from 'lodash-es';
6
6
  import { BrainCog, MessagesSquare } from 'lucide-react';
7
7
  import { useMemo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
+ import SliderWithInput from 'src/components/SliderWithInput';
9
10
  import { shallow } from 'zustand/shallow';
10
11
 
11
12
  import { FORM_STYLE } from '@/const/layoutTokens';
12
- import SliderWithInput from '@/features/SliderWithInput';
13
13
  import { agentSelectors, useSessionStore } from '@/store/session';
14
14
  import { LanguageModel } from '@/types/llm';
15
15
  import type { LobeAgentConfig } from '@/types/session';
@@ -5,11 +5,11 @@ import { debounce } from 'lodash-es';
5
5
  import { AppWindow, BrainCog, MessagesSquare, Palette, Webhook } from 'lucide-react';
6
6
  import { memo, useCallback, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
+ import SliderWithInput from 'src/components/SliderWithInput';
8
9
  import { shallow } from 'zustand/shallow';
9
10
 
10
11
  import { FORM_STYLE } from '@/const/layoutTokens';
11
12
  import AvatarWithUpload from '@/features/AvatarWithUpload';
12
- import SliderWithInput from '@/features/SliderWithInput';
13
13
  import { options } from '@/locales/options';
14
14
  import { settingsSelectors, useSettings } from '@/store/settings';
15
15
  import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
@@ -107,7 +107,7 @@ const SettingForm = memo(() => {
107
107
  children: <Input.Password placeholder={t('settingOpenAI.token.placeholder')} />,
108
108
  desc: t('settingOpenAI.token.desc'),
109
109
  label: t('settingOpenAI.token.title'),
110
- name: 'token',
110
+ name: 'OPENAI_API_KEY',
111
111
  },
112
112
  {
113
113
  children: <Input placeholder={t('settingOpenAI.endpoint.placeholder')} />,
@@ -236,7 +236,7 @@ const SettingForm = memo(() => {
236
236
  children: <Input.Password placeholder={t('settingSystem.accessCode.placeholder')} />,
237
237
  desc: t('settingSystem.accessCode.desc'),
238
238
  label: t('settingSystem.accessCode.title'),
239
- name: 'accessCode',
239
+ name: 'password',
240
240
  },
241
241
  {
242
242
  children: (
@@ -1,16 +1,23 @@
1
1
  import { merge } from 'lodash-es';
2
2
 
3
+ import { OPENAI_API_KEY_HEADER_KEY } from '@/const/fetch';
3
4
  import { initialLobeAgentConfig } from '@/store/session';
5
+ import { useSettings } from '@/store/settings';
4
6
  import type { OpenAIStreamPayload } from '@/types/openai';
5
7
 
6
8
  import { URLS } from './url';
7
9
 
10
+ interface FetchChatModelOptions {
11
+ signal?: AbortSignal | undefined;
12
+ withPlugin?: boolean;
13
+ }
14
+
8
15
  /**
9
16
  * 专门用于对话的 fetch
10
17
  */
11
18
  export const fetchChatModel = (
12
19
  params: Partial<OpenAIStreamPayload>,
13
- options?: { signal?: AbortSignal | undefined; withPlugin?: boolean },
20
+ options?: FetchChatModelOptions,
14
21
  ) => {
15
22
  const payload = merge(
16
23
  {
@@ -25,6 +32,7 @@ export const fetchChatModel = (
25
32
  body: JSON.stringify(payload),
26
33
  headers: {
27
34
  'Content-Type': 'application/json',
35
+ [OPENAI_API_KEY_HEADER_KEY]: useSettings.getState().settings.OPENAI_API_KEY || '',
28
36
  },
29
37
  method: 'POST',
30
38
  signal: options?.signal,
@@ -4,7 +4,7 @@ import { LanguageModel } from '@/types/llm';
4
4
  export type SidebarTabKey = 'chat' | 'market';
5
5
 
6
6
  export const DEFAULT_SETTINGS: ConfigSettings = {
7
- accessCode: '',
7
+ OPENAI_API_KEY: '',
8
8
  avatar: '',
9
9
  compressThreshold: 24,
10
10
  enableCompressThreshold: false,
@@ -18,11 +18,11 @@ export const DEFAULT_SETTINGS: ConfigSettings = {
18
18
  maxTokens: 2000,
19
19
  model: LanguageModel.GPT3_5,
20
20
  neutralColor: '',
21
+ password: '',
21
22
  presencePenalty: 0,
22
23
  primaryColor: '',
23
24
  temperature: 0.5,
24
25
  themeMode: 'auto',
25
- token: '',
26
26
  topP: 1,
27
27
  };
28
28
 
@@ -9,7 +9,7 @@ import { Locales } from './locale';
9
9
  * 配置设置
10
10
  */
11
11
  export interface ConfigSettings {
12
- accessCode: string;
12
+ OPENAI_API_KEY: string;
13
13
  avatar: string;
14
14
  compressThreshold: number;
15
15
  enableCompressThreshold: boolean;
@@ -23,11 +23,11 @@ export interface ConfigSettings {
23
23
  maxTokens: number;
24
24
  model: LanguageModel;
25
25
  neutralColor: NeutralColors | '';
26
+ password: string;
26
27
  presencePenalty: number;
27
28
  primaryColor: PrimaryColors | '';
28
29
  temperature: number;
29
30
  themeMode: ThemeMode;
30
- token: string;
31
31
  topP: number;
32
32
  }
33
33