feat: Add new features and improve user experience

The changes include renaming keys, adding new keys, and modifying key-value pairs in JSON files. Language translations, new components and settings, modifications to existing components, and bug fixes are included. The changes also involve importing and using different components and modules, as well as adding new functionality and features. Some changes involve implementing different input components and their functionalities. New files are added and existing files are deleted. Overall, the changes aim to improve the chat application's settings, customization options, and user experience.
This commit is contained in:
canisminor1990
2023-07-24 18:16:43 +08:00
parent eff42fb181
commit 64c8782ba0
40 changed files with 915 additions and 624 deletions

View File

@@ -1,41 +1,33 @@
{
"DefaultSession": "Default Session",
"advanceSettings": "Advanced Settings",
"agentAvatar": "Avatar",
"agentDescription": "Description",
"agentDescriptionPlaceholder": "Please enter a description",
"agentMaxToken": "Max Token Length",
"agentModel": "Model",
"agentName": "Name",
"agentNamePlaceholder": "Please enter a name",
"agentProfile": "Agent Profile",
"agentPrompt": "AI Prompt",
"agentPromptPlaceholder": "Please enter AI prompt",
"agentTag": "Tag",
"agentTagPlaceholder": "Please enter a tag",
"archive": "Archive",
"archiveCurrentMessages": "Archive Current Session",
"autoGenerate": "Auto Generate",
"autoGenerateTooltip": "Description of the autocomplete assistant based on prompts",
"autoGenerateTooltip": "Auto generate assistant description based on prompts",
"cancel": "Cancel",
"clearCurrentMessages": "Clear Current Session Messages",
"close": "Close",
"confirmRemoveSessionItemAlert": "You are about to remove this agent. Once removed, it cannot be recovered. Please confirm your action.",
"defaultAgent": "Default Agent",
"confirmClearCurrentMessages": "You are about to clear the current session messages. Once cleared, they cannot be recovered. Please confirm your action.",
"confirmRemoveSessionItemAlert": "You are about to delete this assistant. Once deleted, it cannot be recovered. Please confirm your action.",
"defaultAgent": "Default Assistant",
"defaultSession": "Default Session",
"edit": "Edit",
"editAgentProfile": "Edit Agent Profile",
"export": "Export",
"gpt-3.5-turbo": "GPT 3.5 Turbo",
"gpt-3.5-turbo-16k": "GPT 3.5 Turbo (16K)",
"gpt-4": "GPT 4",
"gpt-4-32k": "GPT 4 (32K)",
"modelConfig": "Model Configuration",
"modelTemperature": "Temperature",
"newAgent": "New Agent",
"noDescription": "No description",
"newAgent": "New Assistant",
"noDescription": "No description available",
"ok": "OK",
"profile": "Profile",
"reset": "Reset",
"searchAgentPlaceholder": "Search agents and conversations...",
"sessionSetting": "Session Setting",
"setting": "Setting",
"searchAgentPlaceholder": "Search assistants and sessions...",
"send": "Send",
"sendPlaceholder": "Enter chat content...",
"sessionSetting": "Session Settings",
"setting": "Settings",
"share": "Share",
"updateAgent": "Update Agent",
"updatePrompt": "Update Prompt"
"tokenDetail": "System Role Token: {{systemRoleToken}} Chats Token: {{chatsToken}}",
"updateAgent": "Update Assistant Profile",
"updatePrompt": "Update Prompts"
}

10
locales/en_US/plugin.json Normal file
View File

@@ -0,0 +1,10 @@
{
"pluginList": "Plugin List",
"pluginLoading": "Plugin Loading...",
"plugins": {
"realtimeWeather": "Real-time Weather Forecast",
"searchEngine": "Search Engine",
"undefined": "Plugin Detection...",
"websiteCrawler": "Website Content Extraction"
}
}

View File

@@ -1,4 +1,5 @@
{
"agentHeader": "Edit Assistant Information",
"danger": {
"clear": {
"action": "Clear Now",
@@ -15,6 +16,31 @@
}
},
"header": "Settings",
"settingAgent": {
"avatar": {
"title": "Avatar"
},
"description": {
"placeholder": "Please enter assistant description",
"title": "Assistant Description"
},
"name": {
"placeholder": "Please enter assistant name",
"title": "Name"
},
"prompt": {
"placeholder": "Please enter AI prompt",
"title": "Assistant Role"
},
"tag": {
"placeholder": "Please enter tags",
"title": "Tags"
},
"title": "Assistant Information",
"backgroundColor": {
"title": "Background Color"
}
},
"settingChat": {
"compressThreshold": {
"desc": "When the uncompressed chat history exceeds this value, it will be compressed",
@@ -27,12 +53,13 @@
"title": "Enable History Message Count Limit"
},
"historyCount": {
"desc": "Number of history messages to include in each request",
"desc": "Number of history messages carried in each request",
"title": "History Message Count"
},
"inputTemplate": {
"desc": "The latest user message will be filled into this template",
"title": "User Input Preprocessing"
"title": "User Input Preprocessing",
"placeholder": "Enter pre-processing template, {text} will be replaced with real-time input information"
},
"title": "Chat Settings"
},
@@ -50,6 +77,12 @@
},
"model": {
"desc": "ChatGPT Model",
"list": {
"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)"
},
"title": "Model"
},
"presencePenalty": {
@@ -63,7 +96,7 @@
"title": "Model Settings",
"topP": {
"desc": "Similar to randomness, but do not change together with randomness",
"title": "Top-p Sampling"
"title": "Nucleus Sampling"
}
},
"settingOpenAI": {
@@ -79,10 +112,13 @@
"title": "API Key"
}
},
"settingPlugin": {
"title": "Plugin List"
},
"settingSystem": {
"accessCode": {
"desc": "Encryption access is enabled by the administrator",
"placeholder": "Please enter the access password",
"desc": "Encrypted access has been enabled by the administrator",
"placeholder": "Please enter access password",
"title": "Access Password"
},
"title": "System Settings"
@@ -99,7 +135,7 @@
"title": "Language Settings"
},
"neutralColor": {
"desc": "Custom grayscale for different color biases",
"desc": "Custom grayscale for different color tendencies",
"title": "Neutral Color"
},
"primaryColor": {

View File

@@ -1,41 +1,32 @@
{
"advanceSettings": "高级设置",
"agentAvatar": "头像",
"agentDescription": "描述",
"agentDescriptionPlaceholder": "请输入描述",
"agentMaxToken": "会话最大长度",
"agentModel": "模型",
"agentName": "名称",
"agentNamePlaceholder": "请输入名称",
"agentProfile": "助手信息",
"agentPrompt": "提示词",
"agentPromptPlaceholder": "请输入 AI 提示词",
"agentTag": "标签",
"agentTagPlaceholder": "请输入标签",
"archive": "归档",
"archiveCurrentMessages": "归档当前会话",
"autoGenerate": "自动补全",
"autoGenerateTooltip": "基于提示词自动补全助手描述",
"cancel": "取消",
"clearCurrentMessages": "清空当前会话消息",
"close": "关闭",
"confirmClearCurrentMessages": "即将清空当前会话消息,清空后将无法找回,请确认你的操作",
"confirmRemoveSessionItemAlert": "即将删除该助手,删除后该将无法找回,请确认你的操作",
"defaultAgent": "默认助手",
"defaultSession": "默认对话",
"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": "暂无描述",
"ok": "确定",
"profile": "助手身份",
"reset": "重置",
"searchAgentPlaceholder": "搜索助手和对话...",
"send": "发送",
"sendPlaceholder": "输入聊天内容...",
"sessionSetting": "会话设置",
"setting": "设置",
"share": "分享",
"tokenDetail": "系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}",
"updateAgent": "更新助理信息",
"updatePrompt": "更新提示词"
}

10
locales/zh_CN/plugin.json Normal file
View File

@@ -0,0 +1,10 @@
{
"pluginList": "插件列表",
"pluginLoading": "插件运行中...",
"plugins": {
"realtimeWeather": "实时天气预报",
"searchEngine": "搜索引擎",
"undefined": "插件检测中...",
"websiteCrawler": "网页内容提取"
}
}

View File

@@ -1,4 +1,5 @@
{
"agentHeader": "编辑助手信息",
"danger": {
"clear": {
"action": "立即清除",
@@ -15,6 +16,31 @@
}
},
"header": "设置",
"settingAgent": {
"avatar": {
"title": "头像"
},
"backgroundColor": {
"title": "背景色"
},
"description": {
"placeholder": "请输入助手描述",
"title": "助手描述"
},
"name": {
"placeholder": "请输入助手名称",
"title": "名称"
},
"prompt": {
"placeholder": "请输入 AI 提示词",
"title": "助手角色"
},
"tag": {
"placeholder": "请输入标签",
"title": "标签"
},
"title": "助手信息"
},
"settingChat": {
"compressThreshold": {
"desc": "当未压缩的历史消息超过该值时,将进行压缩",
@@ -32,6 +58,7 @@
},
"inputTemplate": {
"desc": "用户最新的一条消息会填充到此模板",
"placeholder": "输入预处理模版,{text} 将替换为实时输入信息",
"title": "用户输入预处理"
},
"title": "聊天设置"
@@ -50,6 +77,12 @@
},
"model": {
"desc": "ChatGPT 模型",
"list": {
"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)"
},
"title": "模型"
},
"presencePenalty": {
@@ -79,6 +112,9 @@
"title": "API Key"
}
},
"settingPlugin": {
"title": "插件列表"
},
"settingSystem": {
"accessCode": {
"desc": "管理员已开启加密访问",

View File

@@ -2,6 +2,8 @@ import { memo } from 'react';
import { HEADER_HEIGHT } from '@/const/layoutTokens';
const HeaderSpacing = memo(() => <div style={{ flex: 'none', height: HEADER_HEIGHT }} />);
const HeaderSpacing = memo<{ height?: number }>(({ height = HEADER_HEIGHT }) => (
<div style={{ flex: 'none', height }} />
));
export default HeaderSpacing;

View File

@@ -0,0 +1,42 @@
import { createStyles } from 'antd-style';
import { ReactNode, memo } from 'react';
import { Flexbox } from 'react-layout-kit';
const useStyles = createStyles(({ css, token }) => ({
desc: css`
font-size: 12px;
color: ${token.colorTextTertiary};
`,
title: css`
font-size: 16px;
font-weight: bold;
`,
titleWithDesc: css`
font-weight: bold;
`,
}));
export interface HeaderTitleProps {
desc?: string | ReactNode;
tag?: ReactNode;
title: string | ReactNode;
}
const HeaderTitle = memo<HeaderTitleProps>(({ title, desc, tag }) => {
const { styles } = useStyles();
if (desc)
return (
<Flexbox>
<Flexbox align={'center'} className={styles.titleWithDesc} gap={8} horizontal>
{title}
{tag}
</Flexbox>
<Flexbox align={'center'} className={styles.desc} horizontal>
{desc}
</Flexbox>
</Flexbox>
);
return <div className={styles.title}>{title}</div>;
});
export default HeaderTitle;

View File

@@ -1,5 +1,10 @@
import type { FormProps } from '@lobehub/ui';
export const HEADER_HEIGHT = 64;
export const CHAT_TEXTAREA_HEIGHT = 200;
export const CHAT_SIDEBAR_WIDTH = 280;
export const FOLDER_WIDTH = 256;
export const FORM_STYLE: FormProps = {
itemMinWidth: 'max(30%,240px)',
style: { maxWidth: 1024, width: '100%' },
};

View File

@@ -1,53 +1,33 @@
export default {
'DefaultSession': '默认对话',
'advanceSettings': '高级设置',
'agentAvatar': '头像',
'agentDescription': '描述',
'agentDescriptionPlaceholder': '请输入描述',
'agentMaxToken': '会话最大长度',
'agentModel': '模型',
'agentName': '名称',
'agentNamePlaceholder': '请输入名称',
'agentProfile': '助手信息',
'agentPrompt': '提示词',
'agentPromptPlaceholder': '请输入 AI 提示词',
'agentTag': '标签',
'agentTagPlaceholder': '请输入标签',
'archive': '归档',
'archiveCurrentMessages': '归档当前会话',
'autoGenerate': '自动补全',
'autoGenerateTooltip': '基于提示词自动补全助手描述',
'cancel': '取消',
'clearCurrentMessages': '清空当前会话消息',
'close': '关闭',
'confirmClearCurrentMessages': '即将清空当前会话消息,清空后将无法找回,请确认你的操作',
'confirmRemoveSessionItemAlert': '即将删除该助手,删除后该将无法找回,请确认你的操作',
'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': '暂无描述',
'ok': '确定',
'plugin-realtimeWeather': '实时天气预报',
'plugin-searchEngine': '搜索引擎',
'plugin-undefined': '插件检测中...',
'plugin-websiteCrawler': '网页内容提取',
'pluginList': '插件列表',
'pluginLoading': '插件运行中...',
'profile': '助手身份',
'reset': '重置',
'searchAgentPlaceholder': '搜索助手和对话...',
'sessionSetting': '会话设置',
'setting': '设置',
'share': '分享',
'tokenDetail': '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
'updateAgent': '更新助理信息',
'updatePrompt': '更新提示词',
advanceSettings: '高级设置',
agentMaxToken: '会话最大长度',
agentModel: '模型',
agentProfile: '助手信息',
archive: '归档',
archiveCurrentMessages: '归档当前会话',
archiveSearchPlaceholder: '搜索归档对话...',
autoGenerate: '自动补全',
autoGenerateTooltip: '基于提示词自动补全助手描述',
cancel: '取消',
clearCurrentMessages: '清空当前会话消息',
close: '关闭',
confirmClearCurrentMessages: '即将清空当前会话消息,清空后将无法找回,请确认你的操作',
confirmRemoveSessionItemAlert: '即将删除该助手,删除后该将无法找回,请确认你的操作',
defaultAgent: '默认助手',
defaultSession: '默认对话',
edit: '编辑',
export: '导出',
newAgent: '新建助手',
noDescription: '暂无描述',
ok: '确定',
reset: '重置',
roleAndArchive: '角色与记录',
searchAgentPlaceholder: '搜索助手和对话...',
send: '发送',
sendPlaceholder: '输入聊天内容...',
setting: '设置',
share: '分享',
tokenDetail: '系统设定: {{systemRoleToken}} 历史消息: {{chatsToken}}',
updateAgent: '更新助理信息',
updatePrompt: '更新提示词',
};

View File

@@ -0,0 +1,10 @@
export default {
pluginList: '插件列表',
pluginLoading: '插件运行中...',
plugins: {
realtimeWeather: '实时天气预报',
searchEngine: '搜索引擎',
undefined: '插件检测中...',
websiteCrawler: '网页内容提取',
},
};

View File

@@ -14,8 +14,43 @@ export default {
title: '重置所有设置',
},
},
header: '设置',
header: {
global: '全局设置',
session: '会话设置',
},
settingAgent: {
avatar: {
title: '头像',
},
backgroundColor: {
title: '背景色',
},
description: {
placeholder: '请输入助手描述',
title: '助手描述',
},
name: {
placeholder: '请输入助手名称',
title: '名称',
},
prompt: {
placeholder: '请输入 AI 提示词',
title: '助手角色',
},
tag: {
placeholder: '请输入标签',
title: '标签',
},
title: '助手信息',
},
settingChat: {
chatStyleType: {
title: '聊天窗口样式',
type: {
bubble: '气泡模式',
docs: '文档模式',
},
},
compressThreshold: {
desc: '当未压缩的历史消息超过该值时,将进行压缩',
title: '历史消息长度压缩阈值',
@@ -32,6 +67,7 @@ export default {
},
inputTemplate: {
desc: '用户最新的一条消息会填充到此模板',
placeholder: '输入预处理模版,{text} 将替换为实时输入信息',
title: '用户输入预处理',
},
title: '聊天设置',
@@ -50,6 +86,12 @@ export default {
},
model: {
desc: 'ChatGPT 模型',
list: {
'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)',
},
title: '模型',
},
presencePenalty: {
@@ -79,6 +121,9 @@ export default {
title: 'API Key',
},
},
settingPlugin: {
title: '插件列表',
},
settingSystem: {
accessCode: {
desc: '管理员已开启加密访问',

View File

@@ -1,8 +1,10 @@
import common from '../../../locales/en_US/common.json';
import plugin from '../../../locales/en_US/plugin.json';
import setting from '../../../locales/en_US/setting.json';
const resources = {
common,
plugin,
setting,
} as const;

View File

@@ -1,8 +1,10 @@
import common from '../default/common';
import plugin from '../default/plugin';
import setting from '../default/setting';
const resources = {
common,
plugin,
setting,
} as const;

View File

@@ -0,0 +1,26 @@
import { type ReactNode, memo } from 'react';
import { Flexbox } from 'react-layout-kit';
interface HeaderProps {
actions?: ReactNode;
title: string;
}
const Header = memo<HeaderProps>(({ title, actions }) => {
return (
<Flexbox
align={'center'}
distribution={'space-between'}
horizontal
padding={12}
paddingInline={16}
>
<Flexbox>{title}</Flexbox>
<Flexbox gap={4} horizontal>
{actions}
</Flexbox>
</Flexbox>
);
});
export default Header;

View File

@@ -1,64 +0,0 @@
import { Avatar } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { LucideBrain, LucideThermometer, WholeWord } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Center, Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
import { agentSelectors, sessionSelectors, useSessionStore } from '@/store/session';
import { DEFAULT_TITLE } from '@/store/session/slices/agentConfig';
import { ConfigCell, ConfigCellGroup } from './ConfigCell';
const useStyles = createStyles(({ css, token }) => ({
desc: css`
color: ${token.colorText};
`,
model: css`
color: ${token.colorTextTertiary};
`,
title: css`
font-size: ${token.fontSizeHeading4}px;
font-weight: bold;
`,
}));
const ReadMode = memo(() => {
const { styles } = useStyles();
const session = useSessionStore(sessionSelectors.currentSessionSafe, isEqual);
const avatar = useSessionStore(agentSelectors.currentAgentAvatar, shallow);
const title = useSessionStore(agentSelectors.currentAgentTitle, shallow);
const model = useSessionStore(agentSelectors.currentAgentModel, shallow);
const { t } = useTranslation('common');
return (
<Center gap={12} paddingBlock={16} style={{ marginTop: 8 }}>
<Avatar avatar={avatar} size={100} />
<Flexbox className={styles.title}>{title || DEFAULT_TITLE}</Flexbox>
<Flexbox className={styles.model}>{model}</Flexbox>
<Flexbox className={styles.desc}>{session.meta.description}</Flexbox>
<Flexbox flex={1} gap={12} width={'100%'}>
<ConfigCell icon={LucideBrain} label={t('agentPrompt')} />
<ConfigCellGroup
items={[
{
icon: LucideThermometer,
label: t('modelTemperature'),
value: session.config.params.temperature,
},
{
icon: WholeWord,
label: t('agentMaxToken'),
value: session.config.params.max_tokens,
},
]}
/>
</Flexbox>
</Center>
);
});
export default ReadMode;

View File

@@ -0,0 +1,86 @@
import { ActionIcon, DraggablePanelBody, EditableMessage, SearchBar } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { ChevronRight } from 'lucide-react';
import { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
import { agentSelectors, useSessionStore } from '@/store/session';
import Header from './Header';
const useStyles = createStyles(({ css, token }) => ({
desc: css`
color: ${token.colorText};
`,
model: css`
color: ${token.colorTextTertiary};
`,
prompt: css`
overflow-x: hidden;
overflow-y: auto;
height: 200px;
padding: 0 16px 16px;
opacity: 0.75;
border-bottom: 1px solid ${token.colorBorder};
transition: 200ms ${token.motionEaseOut};
&:hover {
opacity: 1;
}
`,
title: css`
font-size: ${token.fontSizeHeading4}px;
font-weight: bold;
`,
}));
const SideBar = memo(() => {
const [openModal, setOpenModal] = useState(false);
const { styles } = useStyles();
const [updateAgentConfig] = useSessionStore((s) => [s.updateAgentConfig], shallow);
const systemRole = useSessionStore(agentSelectors.currentAgentSystemRole, shallow);
const { t } = useTranslation('common');
return (
<DraggablePanelBody style={{ padding: 0 }}>
<Header
actions={
<ActionIcon
icon={ChevronRight}
onClick={() => setOpenModal(true)}
size="small"
title={t('edit')}
/>
}
title={t('settingAgent.prompt.title', { ns: 'setting' })}
/>
<EditableMessage
classNames={{ markdown: styles.prompt }}
onChange={(e) => {
updateAgentConfig({ systemRole: e });
}}
onOpenChange={setOpenModal}
openModal={openModal}
placeholder={`${t('settingAgent.prompt.placeholder', { ns: 'setting' })}...`}
styles={{ markdown: systemRole ? {} : { opacity: 0.5 } }}
text={{
cancel: t('cancel'),
confirm: t('ok'),
edit: t('edit'),
title: t('settingAgent.prompt.title', { ns: 'setting' }),
}}
value={systemRole}
/>
<Flexbox style={{ padding: 16 }}>
<SearchBar placeholder={t('archiveSearchPlaceholder')} />
</Flexbox>
</DraggablePanelBody>
);
});
export default SideBar;

View File

@@ -1,22 +1,13 @@
import {
ActionIcon,
DraggablePanel,
DraggablePanelBody,
DraggablePanelContainer,
} from '@lobehub/ui';
import { DraggablePanel, DraggablePanelContainer } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { LucideEdit, LucideX } from 'lucide-react';
import Router from 'next/router';
import { rgba } from 'polished';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
import HeaderSpacing from '@/components/HeaderSpacing';
import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
import { useSessionStore } from '@/store/session';
import ReadMode from './ReadMode';
import SideBar from './SideBar';
const useStyles = createStyles(({ cx, css, token, stylish }) => ({
drawer: cx(
@@ -31,10 +22,9 @@ const useStyles = createStyles(({ cx, css, token, stylish }) => ({
}));
const Config = () => {
const { t } = useTranslation('common');
const { styles } = useStyles();
const [showAgentSettings, toggleConfig, id] = useSessionStore(
(s) => [s.showAgentSettings, s.toggleConfig, s.activeId],
const [showAgentSettings, toggleConfig] = useSessionStore(
(s) => [s.showAgentSettings, s.toggleConfig],
shallow,
);
@@ -50,37 +40,7 @@ const Config = () => {
>
<HeaderSpacing />
<DraggablePanelContainer style={{ flex: 'none', minWidth: CHAT_SIDEBAR_WIDTH }}>
<Flexbox
align={'center'}
className={styles.header}
distribution={'space-between'}
horizontal
padding={12}
paddingInline={16}
>
<Flexbox>{t('agentProfile')}</Flexbox>
<Flexbox gap={4} horizontal>
<ActionIcon
icon={LucideEdit}
onClick={() => {
Router.push(`/chat/${id}/edit`);
}}
size={{ blockSize: 32, fontSize: 20 }}
title={t('edit')}
/>
<ActionIcon
icon={LucideX}
onClick={() => {
toggleConfig(false);
}}
size={{ blockSize: 32, fontSize: 20 }}
title={t('close')}
/>
</Flexbox>
</Flexbox>
<DraggablePanelBody>
<ReadMode />
</DraggablePanelBody>
<SideBar />
</DraggablePanelContainer>
</DraggablePanel>
);

View File

@@ -16,7 +16,7 @@ const useStyles = createStyles(({ css, token }) => ({
`,
}));
const FunctionMessage = memo(() => {
const { t } = useTranslation();
const { t } = useTranslation('plugin');
const { styles } = useStyles();
return (
<Flexbox className={styles.container} gap={8} horizontal>

View File

@@ -1,6 +1,6 @@
import { ActionIcon } from '@lobehub/ui';
import { Popconfirm } from 'antd';
import { Eraser, Languages } from 'lucide-react';
import { BrainCog, Eraser, Thermometer, Timer } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { shallow } from 'zustand/shallow';
@@ -8,14 +8,22 @@ import { shallow } from 'zustand/shallow';
import { useSessionStore } from '@/store/session';
const InputActions = memo(() => {
const { t } = useTranslation();
const { t } = useTranslation('setting');
const [clearMessage] = useSessionStore((s) => [s.clearMessage], shallow);
return (
<>
<ActionIcon icon={Languages} />
<Popconfirm onConfirm={() => clearMessage()} title={t('confirmClearCurrentMessages')}>
<ActionIcon icon={Eraser} title={t('clearCurrentMessages')} />
<ActionIcon icon={BrainCog} title={t('settingModel.model.title')} />
<ActionIcon icon={Thermometer} title={t('settingModel.temperature.title')} />
<ActionIcon icon={Timer} title={t('settingChat.historyCount.title')} />
<Popconfirm
cancelText={t('cancel', { ns: 'common' })}
okButtonProps={{ danger: true }}
okText={t('ok', { ns: 'common' })}
onConfirm={() => clearMessage()}
title={t('confirmClearCurrentMessages', { ns: 'common' })}
>
<ActionIcon icon={Eraser} title={t('clearCurrentMessages', { ns: 'common' })} />
</Popconfirm>
</>
);

View File

@@ -1,4 +1,4 @@
import { ChatInputArea, DraggablePanel, Icon } from '@lobehub/ui';
import { ChatInputArea, DraggablePanel, Icon, Tooltip } from '@lobehub/ui';
import { Button } from 'antd';
import { Archive } from 'lucide-react';
import { memo, useMemo, useState } from 'react';
@@ -13,7 +13,7 @@ import InputActions from './Action';
import Token from './Token';
const ChatInput = () => {
const { t } = useTranslation();
const { t } = useTranslation('common');
const [expand, setExpand] = useState<boolean>(false);
const [text, setText] = useState('');
@@ -21,7 +21,11 @@ const ChatInput = () => {
const [sendMessage] = useSessionStore((s) => [s.createOrSendMsg], shallow);
const footer = useMemo(
() => <Button icon={<Icon icon={Archive} title={t('archiveCurrentMessages')} />} />,
() => (
<Tooltip title={t('archiveCurrentMessages')}>
<Button icon={<Icon icon={Archive} />} />
</Tooltip>
),
[],
);
@@ -54,6 +58,10 @@ const ChatInput = () => {
onExpandChange={setExpand}
onInputChange={setText}
onSend={sendMessage}
placeholder={t('sendPlaceholder')}
text={{
send: t('send'),
}}
/>
</DraggablePanel>
);

View File

@@ -1,61 +1,45 @@
import { ActionIcon, Avatar, ChatHeader } from '@lobehub/ui';
import { Tag } from 'antd';
import { createStyles } from 'antd-style';
import { ArchiveIcon, MoreVerticalIcon, Share2 } from 'lucide-react';
import { PanelRightClose, PanelRightOpen, Settings, Share2 } from 'lucide-react';
import Router from 'next/router';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
import { agentSelectors, sessionSelectors, useSessionStore } from '@/store/session';
const useStyles = createStyles(({ css, token }) => ({
desc: css`
font-size: 12px;
color: ${token.colorTextTertiary};
`,
title: css`
font-weight: bold;
color: ${token.colorText};
`,
}));
import HeaderTitle from '@/components/HeaderTitle';
import { agentSelectors, useSessionStore } from '@/store/session';
const Header = memo(() => {
const { t } = useTranslation('common');
const [avatar, model] = useSessionStore(
(s) => [agentSelectors.currentAgentAvatar(s), agentSelectors.currentAgentModel(s)],
const [meta, id, modle] = useSessionStore(
(s) => [agentSelectors.currentAgentMeta(s), s.activeId, agentSelectors.currentAgentModel(s)],
shallow,
);
const [meta, id] = useSessionStore((s) => {
const chat = sessionSelectors.currentSession(s);
return [chat?.meta, s.activeId];
}, shallow);
const [
// genShareUrl,
toggleConfig,
] = useSessionStore(
(s) => [
// s.genShareUrl,
s.toggleConfig,
],
const [showAgentSettings, toggleConfig] = useSessionStore(
(s) => [s.showAgentSettings, s.toggleConfig],
shallow,
);
const { styles } = useStyles();
return (
<ChatHeader
left={
<>
<Avatar avatar={avatar} size={40} title={meta?.title} />
<Flexbox>
<Flexbox align={'center'} className={styles.title} gap={8} horizontal>
{meta?.title || t('defaultAgent')}
<Tag bordered={false}>{model}</Tag>
</Flexbox>
<Flexbox className={styles.desc}>{meta?.description || t('noDescription')}</Flexbox>
</Flexbox>
<Avatar
avatar={meta?.avatar}
background={meta?.backgroundColor}
onClick={() => {
Router.push(`/chat/${id}/edit`);
}}
size={40}
style={{ cursor: 'pointer' }}
title={meta?.title}
/>
<HeaderTitle
desc={meta?.description || t('noDescription')}
tag={<Tag>{modle}</Tag>}
title={meta?.title || t('defaultAgent')}
/>
</>
}
right={
@@ -69,12 +53,19 @@ const Header = memo(() => {
size={{ fontSize: 24 }}
title={t('share')}
/>
<ActionIcon icon={ArchiveIcon} size={{ fontSize: 24 }} title={t('archive')} />
<ActionIcon
icon={MoreVerticalIcon}
icon={showAgentSettings ? PanelRightClose : PanelRightOpen}
onClick={() => toggleConfig()}
size={{ fontSize: 24 }}
title={t('sessionSetting')}
title={t('roleAndArchive')}
/>
<ActionIcon
icon={Settings}
onClick={() => {
Router.push(`/chat/${id}/edit`);
}}
size={{ fontSize: 24 }}
title={t('header.session', { ns: 'setting' })}
/>
</>
)

View File

@@ -1,46 +0,0 @@
import { Avatar } from '@lobehub/ui';
import { List, Switch, Tag } from 'antd';
import isEqual from 'fast-deep-equal';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
import pluginList from '@/plugins';
import { agentSelectors, useSessionStore } from '@/store/session';
const PluginList = () => {
const { t } = useTranslation('common');
const config = useSessionStore(agentSelectors.currentAgentConfigSafe, isEqual);
const [toggleAgentPlugin] = useSessionStore((s) => [s.toggleAgentPlugin], shallow);
return (
<List
bordered
dataSource={pluginList}
renderItem={(item) => (
<List.Item>
<List.Item.Meta
avatar={<Avatar avatar={item.avatar} />}
description={item.schema.description}
title={
<Flexbox align={'center'} gap={8} horizontal>
{t(`plugin-${item.name}` as any)} <Tag>{item.name}</Tag>
</Flexbox>
}
/>
<Switch
checked={!config.plugins ? false : config.plugins.includes(item.name)}
onChange={() => {
toggleAgentPlugin(item.name);
}}
/>
</List.Item>
)}
size={'large'}
/>
);
};
export default PluginList;

View File

@@ -1,69 +0,0 @@
import { EditableMessage } from '@lobehub/ui';
import { Button } from 'antd';
import { createStyles } from 'antd-style';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
import { agentSelectors, useSessionStore } from '@/store/session';
import { FormItem } from '../FormItem';
export const useStyles = createStyles(({ css, token }) => ({
input: css`
padding: 12px;
background: ${token.colorFillTertiary};
border: 1px solid ${token.colorPrimaryBorder};
border-radius: 8px;
`,
markdown: css`
padding: 12px;
background: ${token.colorFillTertiary};
`,
}));
const Prompt = () => {
const { t } = useTranslation('common');
const [editing, setEditing] = useState(false);
const { styles } = useStyles();
const systemRole = useSessionStore((s) => {
const config = agentSelectors.currentAgentConfigSafe(s);
return config.systemRole;
}, shallow);
const [updateAgentConfig] = useSessionStore((s) => [s.updateAgentConfig], shallow);
return (
<FormItem label={t('agentPrompt')}>
<Flexbox gap={16}>
<EditableMessage
classNames={styles}
editing={editing}
onChange={(e) => {
updateAgentConfig({ systemRole: e });
}}
onEditingChange={setEditing}
showEditWhenEmpty
value={systemRole}
/>
{!editing && !!systemRole && (
<Flexbox direction={'horizontal-reverse'}>
<Button
onClick={() => {
setEditing(true);
}}
type={'primary'}
>
{t('edit')}
</Button>
</Flexbox>
)}
</Flexbox>
</FormItem>
);
};
export default Prompt;

View File

@@ -1,113 +1,172 @@
import { Collapse, ConfigProvider, InputNumber, Segmented, Slider } from 'antd';
import { Form, ItemGroup } from '@lobehub/ui';
import { Input, Segmented, Select, Switch } from 'antd';
import isEqual from 'fast-deep-equal';
import { debounce } from 'lodash-es';
import { BrainCog, MessagesSquare } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
import { FORM_STYLE } from '@/const/layoutTokens';
import SliderWithInput from '@/features/SliderWithInput';
import { agentSelectors, useSessionStore } from '@/store/session';
import { LanguageModel } from '@/types/llm';
import type { LobeAgentConfig } from '@/types/session';
import { FormItem } from '../FormItem';
import { useStyles } from '../style';
import Plugin from './Plugin';
import Prompt from './Prompt';
type SettingItemGroup = ItemGroup & {
children: {
name?: keyof LobeAgentConfig;
}[];
};
const AgentConfig = () => {
const { t } = useTranslation('common');
const { styles, theme } = useStyles();
const { t } = useTranslation('setting');
const config = useSessionStore(agentSelectors.currentAgentConfigSafe, isEqual);
const [updateAgentConfig] = useSessionStore((s) => [s.updateAgentConfig], shallow);
return (
<ConfigProvider
theme={{
components: {
Segmented: {
colorBgLayout: theme.isDarkMode ? '#111' : '#f1f1f1',
},
// TODO: setting 结构嵌套,现在是扁平的 params: { temperature: 0.6 }
// @ts-ignore
const chat: SettingItemGroup = useMemo(
() => ({
children: [
{
children: (
<Segmented
options={[
{ label: t('settingChat.chatStyleType.type.bubble'), value: 'chat' },
{ label: t('settingChat.chatStyleType.type.docs'), value: 'docs' },
]}
/>
),
label: t('settingChat.chatStyleType.title'),
minWidth: undefined,
name: 'chatStyleType',
},
}}
>
<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'}
value={config.model}
/>
</FormItem>
<Prompt />
<Collapse
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
align={'center'}
distribution={'space-between'}
horizontal
paddingBlock={12}
style={{
borderBottom: `1px solid ${theme.colorBorder}`,
}}
>
<Flexbox className={styles.profile}> {t('pluginList')}</Flexbox>
</Flexbox>
<Plugin />
</Flexbox>
</ConfigProvider>
{
children: <Input placeholder={t('settingChat.inputTemplate.placeholder')} />,
desc: t('settingChat.inputTemplate.desc'),
label: t('settingChat.inputTemplate.title'),
name: 'enableHistoryCount',
},
{
children: <Switch />,
label: t('settingChat.enableHistoryCount.title'),
minWidth: undefined,
name: 'enableHistoryCount',
valuePropName: 'checked',
},
{
children: <SliderWithInput max={32} min={0} />,
desc: t('settingChat.historyCount.desc'),
// @ts-ignore
hidden: !config.params.enableHistoryCount,
label: t('settingChat.historyCount.title'),
name: 'historyCount',
},
{
children: <Switch />,
label: t('settingChat.enableCompressThreshold.title'),
minWidth: undefined,
name: 'enableCompressThreshold',
valuePropName: 'checked',
},
{
children: <SliderWithInput max={32} min={0} />,
desc: t('settingChat.compressThreshold.desc'),
// @ts-ignore
hidden: !config.params.enableCompressThreshold,
label: t('settingChat.compressThreshold.title'),
name: 'compressThreshold',
},
],
icon: MessagesSquare,
title: t('settingChat.title'),
}),
[config],
);
// TODO: setting 结构嵌套,现在是扁平的 params: { temperature: 0.6 }
// @ts-ignore
const model: SettingItemGroup = useMemo(
() => ({
children: [
{
children: (
<Select
options={Object.values(LanguageModel).map((value) => ({
label: value,
value,
}))}
/>
),
desc: t('settingModel.model.desc'),
label: t('settingModel.model.title'),
name: 'model',
tag: 'model',
},
{
children: <SliderWithInput max={1} min={0} step={0.1} />,
desc: t('settingModel.temperature.desc'),
label: t('settingModel.temperature.title'),
name: 'temperature',
tag: 'temperature',
},
{
children: <SliderWithInput max={1} min={0} step={0.1} />,
desc: t('settingModel.topP.desc'),
label: t('settingModel.topP.title'),
name: 'topP',
tag: 'top_p',
},
{
children: <SliderWithInput max={2} min={-2} step={0.1} />,
desc: t('settingModel.presencePenalty.desc'),
label: t('settingModel.presencePenalty.title'),
name: 'presencePenalty',
tag: 'presence_penalty',
},
{
children: <SliderWithInput max={2} min={-2} step={0.1} />,
desc: t('settingModel.frequencyPenalty.desc'),
label: t('settingModel.frequencyPenalty.title'),
name: 'frequencyPenalty',
tag: 'frequency_penalty',
},
{
children: <Switch />,
label: t('settingModel.enableMaxTokens.title'),
minWidth: undefined,
name: 'enableMaxTokens',
valuePropName: 'checked',
},
{
children: <SliderWithInput max={32_000} min={0} step={100} />,
desc: t('settingModel.maxTokens.desc'),
// TODO
// @ts-ignore
hidden: !config?.params?.enableMaxTokens,
label: t('settingModel.maxTokens.title'),
name: 'params.maxTokens',
tag: 'max_tokens',
},
],
icon: BrainCog,
title: t('settingModel.title'),
}),
[config],
);
const items = useMemo(() => [chat, model], [config]);
return (
<Form
initialValues={config}
items={items}
onValuesChange={debounce(updateAgentConfig, 100)}
{...FORM_STYLE}
/>
);
};

View File

@@ -0,0 +1,39 @@
import { ActionIcon } from '@lobehub/ui';
import { Input, InputProps } from 'antd';
import { useTheme } from 'antd-style';
import { Wand2 } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
export interface AutoGenerateInputProps extends InputProps {
loading: boolean;
onGenerate: () => void;
}
const AutoGenerateInput = memo<AutoGenerateInputProps>(({ loading, onGenerate, ...props }) => {
const { t } = useTranslation('common');
const theme = useTheme();
return (
<Input
suffix={
<ActionIcon
active
icon={Wand2}
loading={loading}
onClick={onGenerate}
size={'small'}
style={{
color: theme.colorInfo,
marginRight: -4,
}}
title={t('autoGenerate')}
/>
}
type={'block'}
{...props}
/>
);
});
export default AutoGenerateInput;

View File

@@ -0,0 +1,31 @@
import { Swatches, primaryColorsSwatches } from '@lobehub/ui';
import { memo } from 'react';
import { shallow } from 'zustand/shallow';
import { agentSelectors, useSessionStore } from '@/store/session';
import { DEFAULT_BACKGROUND_COLOR } from '@/store/session/slices/agentConfig';
const BackgroundSwatches = memo(() => {
const [backgroundColor, updateAgentMeta] = useSessionStore(
(s) => [agentSelectors.currentAgentBackgroundColor(s), s.updateAgentMeta],
shallow,
);
const handleSelect = (v: any) => {
if (v) {
updateAgentMeta({ backgroundColor: v });
} else {
updateAgentMeta({ backgroundColor: DEFAULT_BACKGROUND_COLOR });
}
};
return (
<Swatches
activeColor={backgroundColor}
colors={primaryColorsSwatches}
onSelect={handleSelect}
/>
);
});
export default BackgroundSwatches;

View File

@@ -39,8 +39,12 @@ const useStyles = createStyles(({ css, token, prefixCls }) => ({
const EmojiPicker = () => {
const { styles } = useStyles();
const [avatar, updateAgentMeta] = useSessionStore(
(s) => [agentSelectors.currentAgentAvatar(s), s.updateAgentMeta],
const [avatar, backgroundColor, updateAgentMeta] = useSessionStore(
(s) => [
agentSelectors.currentAgentAvatar(s),
agentSelectors.currentAgentBackgroundColor(s),
s.updateAgentMeta,
],
shallow,
);
@@ -65,7 +69,7 @@ const EmojiPicker = () => {
trigger={'click'}
>
<div className={styles.avatar} style={{ width: 'fit-content' }}>
<Avatar avatar={avatar} size={200} />
<Avatar avatar={avatar} background={backgroundColor} size={44} />
</div>
</Popover>
);

View File

@@ -1,21 +1,20 @@
import { ActionIcon, Input, Tooltip } from '@lobehub/ui';
import { Button, Collapse } from 'antd';
import { Form, Icon, type ItemGroup, Tooltip } from '@lobehub/ui';
import { Button } from 'antd';
import isEqual from 'fast-deep-equal';
import { LucideSparkles } from 'lucide-react';
import { UserCircle, Wand2 } from 'lucide-react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { shallow } from 'zustand/shallow';
import { FORM_STYLE } from '@/const/layoutTokens';
import { agentSelectors, useSessionStore } from '@/store/session';
import { FormItem } from '../FormItem';
import { useStyles } from '../style';
import AutoGenerateInput from './AutoGenerateInput';
import BackgroundSwatches from './BackgroundSwatches';
import EmojiPicker from './EmojiPicker';
const AgentMeta = () => {
const { t } = useTranslation('common');
const { styles, theme } = useStyles();
const AgentMeta = memo(() => {
const { t } = useTranslation('setting');
const metaData = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
@@ -39,80 +38,74 @@ const AgentMeta = () => {
);
const basic = [
{ key: 'title', label: t('agentName'), placeholder: t('agentNamePlaceholder') },
{
key: 'title',
label: t('settingAgent.name.title'),
placeholder: t('settingAgent.name.placeholder'),
},
{
key: 'description',
label: t('agentDescription'),
placeholder: t('agentDescriptionPlaceholder'),
label: t('settingAgent.description.title'),
placeholder: t('settingAgent.description.placeholder'),
},
// { key: 'tag', label: t('agentTag'), placeholder: t('agentTagPlaceholder') },
];
return (
<Collapse
defaultActiveKey={hasSystemRole ? ['meta'] : []}
items={[
const meta: ItemGroup = useMemo(
() => ({
children: [
{
children: (
<Flexbox gap={80} horizontal style={{ marginTop: 16 }}>
<Flexbox flex={1} gap={24}>
{basic.map((item) => (
<FormItem key={item.key} label={item.label}>
<Input
onChange={(e) => {
updateAgentMeta({ [item.key]: e.target.value });
}}
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')}>
<EmojiPicker />
</FormItem>
</Flexbox>
),
className: styles.collapseHeader,
extra: (
<Tooltip title={t('autoGenerateTooltip')}>
<Button
disabled={!hasSystemRole}
loading={Object.values(loading).some((i) => !!i)}
onClick={(e: any) => {
e.stopPropagation();
console.log(id);
if (!id) return;
autocompleteSessionAgentMeta(id, true);
}}
size={'large'}
>
{t('autoGenerate')}
</Button>
</Tooltip>
),
key: 'meta',
label: <Flexbox className={styles.profile}>{t('profile')}</Flexbox>,
children: <EmojiPicker />,
label: t('settingAgent.avatar.title'),
minWidth: undefined,
},
]}
/>
{
children: <BackgroundSwatches />,
label: t('settingAgent.backgroundColor.title'),
minWidth: undefined,
},
...basic.map((item) => ({
children: (
<AutoGenerateInput
loading={loading[item.key as keyof typeof loading]}
onChange={(e) => {
updateAgentMeta({ [item.key]: e.target.value });
}}
onGenerate={() => {
autocompleteMeta(item.key as keyof typeof metaData);
}}
placeholder={item.placeholder}
value={metaData[item.key as keyof typeof metaData]}
/>
),
label: item.label,
})),
],
extra: (
<Tooltip title={t('autoGenerateTooltip', { ns: 'common' })}>
<Button
disabled={!hasSystemRole}
icon={<Icon icon={Wand2} />}
loading={Object.values(loading).some((i) => !!i)}
onClick={(e: any) => {
e.stopPropagation();
console.log(id);
if (!id) return;
autocompleteSessionAgentMeta(id, true);
}}
size={'small'}
>
{t('autoGenerate', { ns: 'common' })}
</Button>
</Tooltip>
),
icon: UserCircle,
title: t('settingAgent.title'),
}),
[basic, metaData],
);
};
return <Form items={[meta]} {...FORM_STYLE} />;
});
export default AgentMeta;

View File

@@ -0,0 +1,44 @@
import { Avatar, Form, ItemGroup } from '@lobehub/ui';
import { Switch } from 'antd';
import isEqual from 'fast-deep-equal';
import { ToyBrick } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { shallow } from 'zustand/shallow';
import { FORM_STYLE } from '@/const/layoutTokens';
import pluginList from '@/plugins';
import { agentSelectors, useSessionStore } from '@/store/session';
const PluginList = () => {
const { t } = useTranslation('setting');
const config = useSessionStore(agentSelectors.currentAgentConfigSafe, isEqual);
const toggleAgentPlugin = useSessionStore((s) => s.toggleAgentPlugin, shallow);
const plugin: ItemGroup = useMemo(
() => ({
children: pluginList.map((item) => ({
avatar: <Avatar avatar={item.avatar} />,
children: (
<Switch
checked={!config.plugins ? false : config.plugins.includes(item.name)}
onChange={() => toggleAgentPlugin(item.name)}
/>
),
desc: item.schema.description,
label: t(`plugins.${item.name}` as any, { ns: 'plugin' }),
minWidth: undefined,
tag: item.name,
})),
icon: ToyBrick,
title: t('settingPlugin.title'),
}),
[config],
);
return <Form items={[plugin]} {...FORM_STYLE} />;
};
export default PluginList;

View File

@@ -0,0 +1,48 @@
import { CodeEditor, FormGroup } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { Bot } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { shallow } from 'zustand/shallow';
import { FORM_STYLE } from '@/const/layoutTokens';
import { agentSelectors, useSessionStore } from '@/store/session';
export const useStyles = createStyles(({ css, token }) => ({
input: css`
padding: 12px;
background: ${token.colorFillTertiary};
border: 1px solid ${token.colorPrimaryBorder};
border-radius: 8px;
`,
markdown: css`
padding: 12px;
background: ${token.colorFillTertiary};
`,
}));
const AgentPrompt = memo(() => {
const { t } = useTranslation('setting');
const systemRole = useSessionStore(agentSelectors.currentAgentSystemRole, shallow);
const [updateAgentConfig] = useSessionStore((s) => [s.updateAgentConfig], shallow);
return (
<FormGroup icon={Bot} style={FORM_STYLE.style} title={t('settingAgent.prompt.title')}>
<CodeEditor
language={'md'}
onValueChange={(e) => {
updateAgentConfig({ systemRole: e });
}}
placeholder={t('settingAgent.name.placeholder')}
resize={false}
style={{ marginBottom: 16, marginTop: 16 }}
type={'pure'}
value={systemRole}
/>
</FormGroup>
);
});
export default AgentPrompt;

View File

@@ -0,0 +1,31 @@
import { ActionIcon, ChatHeader } from '@lobehub/ui';
import { Download, Share2 } from 'lucide-react';
import Router from 'next/router';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import HeaderTitle from '@/components/HeaderTitle';
const Header = memo(() => {
const { t } = useTranslation('setting');
return (
<ChatHeader
left={<HeaderTitle title={t('header.session')} />}
onBackClick={() => Router.back()}
right={
<>
<ActionIcon icon={Share2} size={{ fontSize: 24 }} title={t('share', { ns: 'common' })} />
<ActionIcon
icon={Download}
size={{ fontSize: 24 }}
title={t('export', { ns: 'common' })}
/>
</>
}
showBackButton
/>
);
});
export default Header;

View File

@@ -1,43 +1,23 @@
import { ActionIcon, ChatHeader } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { Download, Share2 } from 'lucide-react';
import Head from 'next/head';
import Router from 'next/router';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import HeaderSpacing from '@/components/HeaderSpacing';
import { HEADER_HEIGHT } from '@/const/layoutTokens';
import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
import ChatLayout from '../../layout';
import AgentConfig from './AgentConfig';
import AgentMeta from './AgentMeta';
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};
`,
title: css`
font-size: 16px;
font-weight: 500;
`,
}));
import AgentPlugin from './AgentPlugin';
import AgentPrompt from './AgentPrompt';
import Header from './Header';
const EditPage = memo(() => {
const { t } = useTranslation('common');
const { t } = useTranslation('setting');
const { styles } = useStyles();
const pageTitle = t('editAgentProfile');
const pageTitle = genSiteHeadTitle(t('header.session'));
return (
<>
@@ -45,21 +25,13 @@ const EditPage = memo(() => {
<title>{pageTitle}</title>
</Head>
<ChatLayout>
<ChatHeader
left={<div className={styles.title}>{t('editAgentProfile')}</div>}
onBackClick={() => Router.back()}
right={
<>
<ActionIcon icon={Share2} size={{ fontSize: 24 }} title={t('share')} />
<ActionIcon icon={Download} size={{ fontSize: 24 }} title={t('export')} />
</>
}
showBackButton
/>
<Flexbox className={styles.form} flex={1} gap={10} padding={24}>
<HeaderSpacing />
<Header />
<Flexbox align={'center'} flex={1} gap={16} padding={24} style={{ overflow: 'auto' }}>
<HeaderSpacing height={HEADER_HEIGHT - 16} />
<AgentPrompt />
<AgentMeta />
<AgentConfig />
<AgentPlugin />
</Flexbox>
</ChatLayout>
</>

View File

@@ -4,6 +4,7 @@ import { memo } from 'react';
import { Flexbox } from 'react-layout-kit';
import { sessionSelectors, useSessionStore } from '@/store/session';
import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
import Layout from '../layout';
import Config from './Config';
@@ -16,7 +17,7 @@ const Chat = memo(() => {
return [context?.meta.title];
}, isEqual);
const pageTitle = title ? `${title} - LobeChat` : 'LobeChat';
const pageTitle = genSiteHeadTitle(title);
return (
<>

View File

@@ -1,23 +1,16 @@
import { ChatHeader } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import Router from 'next/router';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import HeaderTitle from '@/components/HeaderTitle';
const useStyles = createStyles(({ css, token }) => ({
title: css`
font-size: 16px;
font-weight: bold;
color: ${token.colorText};
`,
}));
const Header = memo(() => {
const { t } = useTranslation('setting');
const { styles } = useStyles();
return (
<ChatHeader
left={<div className={styles.title}>{t('header')}</div>}
left={<HeaderTitle title={t('header.global')} />}
onBackClick={() => Router.back()}
showBackButton
/>

View File

@@ -2,11 +2,12 @@ import { Form, type ItemGroup, ThemeSwitch } from '@lobehub/ui';
import { Form as AntForm, Button, Input, Popconfirm, Select, Switch } from 'antd';
import isEqual from 'fast-deep-equal';
import { debounce } from 'lodash-es';
import { AppWindow, Bot, MessagesSquare, Palette, Webhook } from 'lucide-react';
import { AppWindow, BrainCog, MessagesSquare, Palette, Webhook } from 'lucide-react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { shallow } from 'zustand/shallow';
import { FORM_STYLE } from '@/const/layoutTokens';
import AvatarWithUpload from '@/features/AvatarWithUpload';
import SliderWithInput from '@/features/SliderWithInput';
import { options } from '@/locales/options';
@@ -99,7 +100,7 @@ const SettingForm = memo(() => {
[settings],
);
const OpenAI: SettingItemGroup = useMemo(
const openAI: SettingItemGroup = useMemo(
() => ({
children: [
{
@@ -123,7 +124,7 @@ const SettingForm = memo(() => {
[settings],
);
const Chat: SettingItemGroup = useMemo(
const chat: SettingItemGroup = useMemo(
() => ({
children: [
{
@@ -161,7 +162,7 @@ const SettingForm = memo(() => {
[settings],
);
const Model: SettingItemGroup = useMemo(
const model: SettingItemGroup = useMemo(
() => ({
children: [
{
@@ -222,13 +223,13 @@ const SettingForm = memo(() => {
tag: 'max_tokens',
},
],
icon: Bot,
icon: BrainCog,
title: t('settingModel.title'),
}),
[settings],
);
const System: SettingItemGroup = useMemo(
const system: SettingItemGroup = useMemo(
() => ({
children: [
{
@@ -282,16 +283,15 @@ const SettingForm = memo(() => {
[settings],
);
const items = useMemo(() => [theme, OpenAI, Chat, Model, System], [settings]);
const items = useMemo(() => [theme, openAI, chat, model, system], [settings]);
return (
<Form
form={form}
initialValues={settings}
itemMinWidth="max(30%,240px)"
items={items}
onValuesChange={debounce(setSettings, 100)}
style={{ maxWidth: 1024, width: '100%' }}
{...FORM_STYLE}
/>
);
});

View File

@@ -6,6 +6,7 @@ import { Flexbox } from 'react-layout-kit';
import HeaderSpacing from '@/components/HeaderSpacing';
import { createI18nNext } from '@/locales/create';
import ChatLayout from '@/pages/chat/layout';
import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
import Header from './Header';
import SettingForm from './SettingForm';
@@ -14,7 +15,7 @@ const initI18n = createI18nNext('setting');
const SettingLayout = memo(() => {
const { t } = useTranslation('setting');
const pageTitle = `${t('header')} - LobeChat`;
const pageTitle = genSiteHeadTitle(t('header.global'));
useEffect(() => {
initI18n.finally();

View File

@@ -16,9 +16,11 @@ export const initialLobeAgentConfig: LobeAgentConfig = {
systemRole: '',
};
export const DEFAULT_AVATAR = 'https://npm.elemecdn.com/@lobehub/assets-logo/assets/logo-3d.webp';
export const DEFAULT_AVATAR = '🤖';
export const DEFAULT_TITLE = 'DefaultSession';
export const DEFAULT_BACKGROUND_COLOR = 'rgba(0,0,0,0)';
export const DEFAULT_TITLE = 'defaultSession';
export const initialAgentConfigState: AgentConfigState = {
// // loading 中间态
@@ -30,5 +32,5 @@ export const initialAgentConfigState: AgentConfigState = {
title: false,
},
showAgentSettings: false,
showAgentSettings: true,
};

View File

@@ -4,27 +4,30 @@ import { MetaData } from '@/types/meta';
import { LobeAgentConfig } from '@/types/session';
import { sessionSelectors } from '../session';
import { DEFAULT_AVATAR, initialLobeAgentConfig } from './initialState';
import { DEFAULT_AVATAR, DEFAULT_BACKGROUND_COLOR, initialLobeAgentConfig } from './initialState';
const currentAgentMeta = (s: SessionStore): MetaData => {
const session = sessionSelectors.currentSession(s);
return session?.meta || {};
return { avatar: DEFAULT_AVATAR, backgroundColor: DEFAULT_BACKGROUND_COLOR, ...session?.meta };
};
const currentAgentTitle = (s: SessionStore) => currentAgentMeta(s)?.title;
const currentAgentBackgroundColor = (s: SessionStore) => {
const session = sessionSelectors.currentSession(s);
if (!session) return DEFAULT_BACKGROUND_COLOR;
return session.meta.backgroundColor || DEFAULT_BACKGROUND_COLOR;
};
const currentAgentAvatar = (s: SessionStore) => {
const session = sessionSelectors.currentSession(s);
if (!session) return DEFAULT_AVATAR;
return session.meta.avatar || DEFAULT_AVATAR;
};
const currentAgentConfig = (s: SessionStore) => {
const session = sessionSelectors.currentSession(s);
return session?.config;
};
@@ -32,6 +35,10 @@ const currentAgentConfigSafe = (s: SessionStore): LobeAgentConfig => {
return currentAgentConfig(s) || initialLobeAgentConfig;
};
const currentAgentSystemRole = (s: SessionStore) => {
return currentAgentConfigSafe(s).systemRole;
};
const currentAgentModel = (s: SessionStore): LanguageModel => {
const config = currentAgentConfig(s);
@@ -45,10 +52,12 @@ const hasSystemRole = (s: SessionStore) => {
};
export const agentSelectors = {
currentAgentAvatar,
currentAgentBackgroundColor,
currentAgentConfig,
currentAgentConfigSafe,
currentAgentMeta,
currentAgentModel,
currentAgentSystemRole,
currentAgentTitle,
hasSystemRole,
};

View File

@@ -0,0 +1 @@
export const genSiteHeadTitle = (title?: string) => (title ? `${title} - LobeChat` : 'LobeChat');