mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
✨ feat: 增加不同模型
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
"agentAvatar": "头像",
|
||||
"agentDescription": "描述",
|
||||
"agentDescriptionPlaceholder": "请输入描述",
|
||||
"agentModel": "模型",
|
||||
"agentName": "名称",
|
||||
"agentNamePlaceholder": "请输入名称",
|
||||
"agentProfile": "助手信息",
|
||||
@@ -18,7 +19,12 @@
|
||||
"defaultAgent": "默认助手",
|
||||
"edit": "编辑",
|
||||
"editAgentProfile": "编辑助手信息",
|
||||
|
||||
"export": "导出",
|
||||
"gpt-3.5-turbo": "GPT 3.5",
|
||||
"gpt-3.5-turbo-16k": "GPT 3.5 (16K)",
|
||||
"gpt-4": "GPT 4",
|
||||
"gpt-4-32k": "GPT 4 (32K)",
|
||||
"modelConfig": "模型配置",
|
||||
"modelTemperature": "发散度",
|
||||
"newAgent": "新建助手",
|
||||
"noDescription": "暂无描述",
|
||||
@@ -29,5 +35,6 @@
|
||||
"sessionSetting": "会话设置",
|
||||
"setting": "设置",
|
||||
"share": "分享",
|
||||
"updateAgent": "更新助理信息"
|
||||
"updateAgent": "更新助理信息",
|
||||
"updatePrompt": "更新提示词"
|
||||
}
|
||||
|
||||
@@ -86,7 +86,6 @@ export function OpenAIStream(payload: OpenAIStreamPayload) {
|
||||
{
|
||||
streaming: true,
|
||||
...params,
|
||||
|
||||
callbacks: [
|
||||
{
|
||||
handleLLMNewToken(token) {
|
||||
|
||||
@@ -13,7 +13,7 @@ const SessionList = memo(() => {
|
||||
const { styles, cx } = useStyles();
|
||||
const list = useSessionStore((s) => sessionSelectors.chatList(s), isEqual);
|
||||
const [activeId, loading] = useSessionStore(
|
||||
(s) => [s.activeId, s.loading.summarizingTitle],
|
||||
(s) => [s.activeId, s.autocompleteLoading.title],
|
||||
shallow,
|
||||
);
|
||||
|
||||
|
||||
106
src/pages/chat/[id]/edit/AgentConfig.tsx
Normal file
106
src/pages/chat/[id]/edit/AgentConfig.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { TextArea } from '@lobehub/ui';
|
||||
import { Button, Collapse, InputNumber, Segmented, Slider } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { agentSelectors, useSessionStore } from '@/store/session';
|
||||
import { LanguageModel } from '@/types/llm';
|
||||
|
||||
import { FormItem } from './FormItem';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const AgentConfig = () => {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const { styles, theme } = useStyles();
|
||||
|
||||
const config = useSessionStore(agentSelectors.currentAgentConfigSafe, isEqual);
|
||||
|
||||
const [updateAgentConfig] = useSessionStore((s) => [s.updateAgentConfig], shallow);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
distribution={'space-between'}
|
||||
horizontal
|
||||
paddingBlock={12}
|
||||
style={{
|
||||
borderBottom: `1px solid ${theme.colorBorder}`,
|
||||
}}
|
||||
>
|
||||
<Flexbox className={styles.profile}> {t('modelConfig')}</Flexbox>
|
||||
</Flexbox>
|
||||
<Flexbox gap={24}>
|
||||
<FormItem label={t('agentModel')}>
|
||||
<Segmented
|
||||
block
|
||||
onChange={(value) => {
|
||||
updateAgentConfig({ model: value as LanguageModel });
|
||||
}}
|
||||
options={Object.values(LanguageModel).map((value) => ({
|
||||
label: t(value),
|
||||
value,
|
||||
}))}
|
||||
size={'large'}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label={t('agentPrompt')}>
|
||||
<Flexbox gap={16}>
|
||||
<TextArea
|
||||
placeholder={t('agentPromptPlaceholder')}
|
||||
style={{ minHeight: 160 }}
|
||||
type={'block'}
|
||||
value={config.systemRole}
|
||||
/>
|
||||
<Flexbox direction={'horizontal-reverse'}>
|
||||
<Button type={'primary'}>{t('updatePrompt')}</Button>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
</FormItem>
|
||||
<Collapse
|
||||
activeKey={['advanceSettings']}
|
||||
bordered={false}
|
||||
className={styles.title}
|
||||
expandIconPosition={'end'}
|
||||
items={[
|
||||
{
|
||||
children: (
|
||||
<Flexbox paddingBlock={16}>
|
||||
<FormItem label={t('modelTemperature')}>
|
||||
<Flexbox gap={16} horizontal>
|
||||
<Slider
|
||||
max={1}
|
||||
min={0}
|
||||
onChange={(value) => {
|
||||
updateAgentConfig({ params: { temperature: value } });
|
||||
}}
|
||||
step={0.1}
|
||||
style={{ flex: 1 }}
|
||||
value={Number(config.params.temperature)}
|
||||
/>
|
||||
<InputNumber
|
||||
max={1}
|
||||
min={0}
|
||||
onChange={(value) => {
|
||||
if (value) updateAgentConfig({ params: { temperature: value } });
|
||||
}}
|
||||
value={config.params.temperature}
|
||||
/>
|
||||
</Flexbox>
|
||||
</FormItem>
|
||||
</Flexbox>
|
||||
),
|
||||
key: 'advanceSettings',
|
||||
label: t('advanceSettings'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Flexbox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentConfig;
|
||||
84
src/pages/chat/[id]/edit/AgentMeta.tsx
Normal file
84
src/pages/chat/[id]/edit/AgentMeta.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ActionIcon, Avatar, Input } from '@lobehub/ui';
|
||||
import { Button } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { LucideSparkles } from 'lucide-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { agentSelectors, useSessionStore } from '@/store/session';
|
||||
|
||||
import { FormItem } from './FormItem';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const AgentMeta = () => {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const { styles, theme } = useStyles();
|
||||
|
||||
const metaData = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
|
||||
|
||||
const [autocompleteMeta, loading] = useSessionStore(
|
||||
(s) => [s.autocompleteMeta, s.autocompleteLoading],
|
||||
shallow,
|
||||
);
|
||||
|
||||
const basic = [
|
||||
{ key: 'title', label: t('agentName'), placeholder: t('agentNamePlaceholder') },
|
||||
{
|
||||
key: 'description',
|
||||
label: t('agentDescription'),
|
||||
placeholder: t('agentDescriptionPlaceholder'),
|
||||
},
|
||||
// { key: 'tag', label: t('agentTag'), placeholder: t('agentTagPlaceholder') },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
distribution={'space-between'}
|
||||
horizontal
|
||||
paddingBlock={12}
|
||||
style={{
|
||||
borderBottom: `1px solid ${theme.colorBorder}`,
|
||||
}}
|
||||
>
|
||||
<Flexbox className={styles.profile}> {t('profile')}</Flexbox>
|
||||
<Button size={'large'}>{t('autoGenerate')}</Button>
|
||||
</Flexbox>
|
||||
<Flexbox gap={80} horizontal style={{ marginTop: 16 }}>
|
||||
<Flexbox flex={1} gap={24}>
|
||||
{basic.map((item) => (
|
||||
<FormItem key={item.key} label={item.label}>
|
||||
<Input
|
||||
placeholder={item.placeholder}
|
||||
suffix={
|
||||
<ActionIcon
|
||||
icon={LucideSparkles}
|
||||
loading={loading[item.key as keyof typeof loading]}
|
||||
onClick={() => {
|
||||
autocompleteMeta(item.key as keyof typeof metaData);
|
||||
}}
|
||||
size={'small'}
|
||||
style={{
|
||||
color: theme.purple,
|
||||
}}
|
||||
title={t('autoGenerate')}
|
||||
/>
|
||||
}
|
||||
type={'block'}
|
||||
value={metaData[item.key as keyof typeof metaData]}
|
||||
/>
|
||||
</FormItem>
|
||||
))}
|
||||
</Flexbox>
|
||||
<FormItem label={t('agentAvatar')}>
|
||||
<Avatar avatar={metaData.avatar} size={200} />
|
||||
</FormItem>
|
||||
</Flexbox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentMeta;
|
||||
@@ -13,7 +13,8 @@ interface FormItemProps {
|
||||
children: ReactNode;
|
||||
label: string;
|
||||
}
|
||||
const FormItem = memo<FormItemProps>(({ label, children }) => {
|
||||
|
||||
export const FormItem = memo<FormItemProps>(({ label, children }) => {
|
||||
const { styles } = useStyles();
|
||||
|
||||
return (
|
||||
@@ -23,5 +24,3 @@ const FormItem = memo<FormItemProps>(({ label, children }) => {
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
export default FormItem;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Avatar, ChatHeader, Input, TextArea } from '@lobehub/ui';
|
||||
import { Button, Slider } from 'antd';
|
||||
import { ChatHeader } from '@lobehub/ui';
|
||||
import { Button } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
@@ -8,7 +8,8 @@ import { memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import ChatLayout from '../../layout';
|
||||
import FormItem from './Form';
|
||||
import AgentConfig from './AgentConfig';
|
||||
import AgentMeta from './AgentMeta';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
footer: css`
|
||||
@@ -23,10 +24,6 @@ const useStyles = createStyles(({ css, token }) => ({
|
||||
background: ${token.colorBgContainer};
|
||||
border-bottom: 1px solid ${token.colorSplit};
|
||||
`,
|
||||
profile: css`
|
||||
font-size: 20px;
|
||||
color: ${token.colorTextTertiary};
|
||||
`,
|
||||
title: css`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
@@ -36,13 +33,8 @@ const useStyles = createStyles(({ css, token }) => ({
|
||||
const EditPage = memo(() => {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const { styles, theme } = useStyles();
|
||||
const { styles } = useStyles();
|
||||
|
||||
const basic = [
|
||||
{ label: t('agentName'), placeholder: t('agentNamePlaceholder') },
|
||||
{ label: t('agentDescription'), placeholder: t('agentDescriptionPlaceholder') },
|
||||
{ label: t('agentTag'), placeholder: t('agentTagPlaceholder') },
|
||||
];
|
||||
return (
|
||||
<ChatLayout>
|
||||
<Flexbox height={'100vh'} style={{ position: 'relative' }} width={'100%'}>
|
||||
@@ -50,59 +42,18 @@ const EditPage = memo(() => {
|
||||
<ChatHeader
|
||||
left={<div className={styles.title}>{t('editAgentProfile')}</div>}
|
||||
onBackClick={() => Router.back()}
|
||||
right={
|
||||
<>
|
||||
<Button>{t('share')}</Button>
|
||||
<Button type={'primary'}>{t('export')}</Button>
|
||||
</>
|
||||
}
|
||||
showBackButton
|
||||
/>
|
||||
{/*form*/}
|
||||
<Flexbox className={styles.form} flex={1} gap={10} padding={24}>
|
||||
<FormItem label={t('agentPrompt')}>
|
||||
<TextArea
|
||||
placeholder={t('agentPromptPlaceholder')}
|
||||
// style={{ minHeight: 64 }}
|
||||
type={'block'}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
distribution={'space-between'}
|
||||
horizontal
|
||||
paddingBlock={12}
|
||||
style={{
|
||||
borderBottom: `1px solid ${theme.colorBorder}`,
|
||||
}}
|
||||
>
|
||||
<Flexbox className={styles.profile}> {t('profile')}</Flexbox>
|
||||
<Button size={'large'}>{t('autoGenerate')}</Button>
|
||||
</Flexbox>
|
||||
<Flexbox gap={80} horizontal style={{ marginTop: 16 }}>
|
||||
<Flexbox flex={1} gap={24}>
|
||||
{basic.map((item) => (
|
||||
<FormItem key={item.label} label={item.label}>
|
||||
<Input placeholder={item.placeholder} type={'block'} />
|
||||
</FormItem>
|
||||
))}
|
||||
|
||||
<Flexbox className={styles.profile}> {t('advanceSettings')}</Flexbox>
|
||||
|
||||
<FormItem label={t('modelTemperature')}>
|
||||
<Slider />
|
||||
</FormItem>
|
||||
</Flexbox>
|
||||
<FormItem label={t('agentAvatar')}>
|
||||
<Avatar size={200} />
|
||||
</FormItem>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
|
||||
{/*bottom*/}
|
||||
<Flexbox
|
||||
className={styles.footer}
|
||||
direction={'horizontal-reverse'}
|
||||
gap={8}
|
||||
padding={'16px 24px'}
|
||||
>
|
||||
<Button type={'primary'}>{t('updateAgent')}</Button>
|
||||
<Button>{t('reset')}</Button>
|
||||
<AgentMeta />
|
||||
<AgentConfig />
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
</ChatLayout>
|
||||
|
||||
33
src/pages/chat/[id]/edit/style.ts
Normal file
33
src/pages/chat/[id]/edit/style.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => ({
|
||||
footer: css`
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
border-top: 1px solid ${token.colorBorder};
|
||||
`,
|
||||
form: css`
|
||||
overflow-y: auto;
|
||||
`,
|
||||
header: css`
|
||||
background: ${token.colorBgContainer};
|
||||
border-bottom: 1px solid ${token.colorSplit};
|
||||
`,
|
||||
profile: css`
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: ${token.colorText};
|
||||
`,
|
||||
title: css`
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
|
||||
.ant-collapse-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 0 !important;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
@@ -24,13 +24,14 @@ export interface AgentAction {
|
||||
* @returns 一个 Promise,用于异步操作完成后的处理
|
||||
*/
|
||||
autocompleteAgentDescription: (id: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* 自动完成代理标题
|
||||
* @param id - 代理的 ID
|
||||
* @returns 一个 Promise,用于异步操作完成后的处理
|
||||
*/
|
||||
autocompleteAgentTitle: (id: string) => Promise<void>;
|
||||
|
||||
autocompleteMeta: (key: keyof MetaData) => void;
|
||||
/**
|
||||
* 自动完成会话代理元数据
|
||||
* @param id - 代理的 ID
|
||||
@@ -77,7 +78,7 @@ export const createAgentSlice: StateCreator<
|
||||
|
||||
const emoji = await fetchPresetTaskResult({
|
||||
onLoadingChange: (loading) => {
|
||||
get().updateLoadingState('pickingEmojiAvatar', loading);
|
||||
get().updateLoadingState('avatar', loading);
|
||||
},
|
||||
params: promptPickEmoji(systemRole),
|
||||
});
|
||||
@@ -111,7 +112,7 @@ export const createAgentSlice: StateCreator<
|
||||
});
|
||||
},
|
||||
onLoadingChange: (loading) => {
|
||||
updateLoadingState('summarizingDescription', loading);
|
||||
updateLoadingState('description', loading);
|
||||
},
|
||||
onMessageHandle: internalUpdateAgentMeta(id)('description'),
|
||||
params: promptSummaryDescription(chats),
|
||||
@@ -137,13 +138,34 @@ export const createAgentSlice: StateCreator<
|
||||
dispatchSession({ id, key: 'title', type: 'updateSessionMeta', value: previousTitle });
|
||||
},
|
||||
onLoadingChange: (loading) => {
|
||||
updateLoadingState('summarizingTitle', loading);
|
||||
updateLoadingState('title', loading);
|
||||
},
|
||||
onMessageHandle: internalUpdateAgentMeta(id)('title'),
|
||||
params: promptSummaryTitle(chats),
|
||||
});
|
||||
},
|
||||
|
||||
autocompleteMeta: (key) => {
|
||||
const { activeId, autoPickEmoji, autocompleteAgentTitle, autocompleteAgentDescription } = get();
|
||||
if (!activeId) return;
|
||||
|
||||
switch (key) {
|
||||
case 'avatar': {
|
||||
autoPickEmoji(activeId);
|
||||
return;
|
||||
}
|
||||
|
||||
case 'description': {
|
||||
autocompleteAgentDescription(activeId);
|
||||
return;
|
||||
}
|
||||
|
||||
case 'title': {
|
||||
autocompleteAgentTitle(activeId);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
autocompleteSessionAgentMeta: (id) => {
|
||||
const session = sessionSelectors.getSessionById(id)(get());
|
||||
|
||||
@@ -160,7 +182,6 @@ export const createAgentSlice: StateCreator<
|
||||
get().autoPickEmoji(id);
|
||||
}
|
||||
},
|
||||
|
||||
internalUpdateAgentMeta: (id: string) => (key: keyof MetaData) => {
|
||||
let value = '';
|
||||
return (text: string) => {
|
||||
@@ -183,6 +204,6 @@ export const createAgentSlice: StateCreator<
|
||||
get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
|
||||
},
|
||||
updateLoadingState: (key, value) => {
|
||||
set({ loading: { ...get().loading, [key]: value } });
|
||||
set({ autocompleteLoading: { ...get().autocompleteLoading, [key]: value } });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { LanguageModel } from '@/types/llm';
|
||||
import { MetaData } from '@/types/meta';
|
||||
import { LobeAgentConfig } from '@/types/session';
|
||||
|
||||
export interface SessionLoadingState {
|
||||
pickingEmojiAvatar: boolean;
|
||||
summarizingDescription: boolean;
|
||||
summarizingTitle: boolean;
|
||||
}
|
||||
export type SessionLoadingState = Record<Partial<keyof MetaData>, boolean>;
|
||||
|
||||
export interface AgentConfigState {
|
||||
loading: SessionLoadingState;
|
||||
autocompleteLoading: SessionLoadingState;
|
||||
|
||||
showAgentSettings: boolean;
|
||||
}
|
||||
@@ -24,10 +22,12 @@ export const DEFAULT_TITLE = '默认对话';
|
||||
|
||||
export const initialAgentConfigState: AgentConfigState = {
|
||||
// // loading 中间态
|
||||
loading: {
|
||||
pickingEmojiAvatar: false,
|
||||
summarizingDescription: false,
|
||||
summarizingTitle: false,
|
||||
autocompleteLoading: {
|
||||
avatar: false,
|
||||
backgroundColor: false,
|
||||
description: false,
|
||||
tag: false,
|
||||
title: false,
|
||||
},
|
||||
|
||||
showAgentSettings: false,
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { SessionStore } from '@/store/session';
|
||||
import { LanguageModel } from '@/types/llm';
|
||||
import { MetaData } from '@/types/meta';
|
||||
import { LobeAgentConfig } from '@/types/session';
|
||||
|
||||
import { sessionSelectors } from '../session';
|
||||
import { DEFAULT_AVATAR, initialLobeAgentConfig } from './initialState';
|
||||
|
||||
const currentAgentTitle = (s: SessionStore) => {
|
||||
const currentAgentMeta = (s: SessionStore): MetaData => {
|
||||
const session = sessionSelectors.currentSession(s);
|
||||
|
||||
return session?.meta.title;
|
||||
return session?.meta || {};
|
||||
};
|
||||
|
||||
const currentAgentTitle = (s: SessionStore) => currentAgentMeta(s)?.title;
|
||||
|
||||
const currentAgentAvatar = (s: SessionStore) => {
|
||||
const session = sessionSelectors.currentSession(s);
|
||||
|
||||
@@ -39,6 +42,7 @@ export const agentSelectors = {
|
||||
currentAgentAvatar,
|
||||
currentAgentConfig,
|
||||
currentAgentConfigSafe,
|
||||
currentAgentMeta,
|
||||
currentAgentModel,
|
||||
currentAgentTitle,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { StateCreator } from 'zustand/vanilla';
|
||||
|
||||
import { fetchChatModel } from '@/services/chatModel';
|
||||
import { SessionStore, chatSelectors, sessionSelectors } from '@/store/session';
|
||||
import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
|
||||
import { ChatMessage } from '@/types/chatMessage';
|
||||
import { FetchSSEOptions, fetchSSE } from '@/utils/fetch';
|
||||
import { nanoid } from '@/utils/uuid';
|
||||
@@ -83,8 +83,9 @@ export const createChatSlice: StateCreator<
|
||||
|
||||
generateMessage: async (messages, options) => {
|
||||
set({ chatLoading: true });
|
||||
const config = agentSelectors.currentAgentConfigSafe(get());
|
||||
|
||||
const fetcher = () => fetchChatModel({ messages });
|
||||
const fetcher = () => fetchChatModel({ messages, model: config.model, ...config.params });
|
||||
|
||||
await fetchSSE(fetcher, options);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user