🐛 fix: agent profiles update, agent tools config set, editor placeholder (#11074)

* feat: open the gtd & document tools in normal agent

* feat: add getAllbuildintools in agent profles tools settings

* fix: slove the tools modal segment not work

* feat: support editor placeholder
This commit is contained in:
Shinji-Li
2025-12-31 16:45:20 +08:00
committed by GitHub
parent f26bbc56de
commit f7cbfe4497
16 changed files with 338 additions and 647 deletions
+1
View File
@@ -215,6 +215,7 @@
"settingAgent.name.placeholder": "Enter agent name",
"settingAgent.name.title": "Name",
"settingAgent.prompt.placeholder": "Enter agent settings, press / to open the command menu",
"settingAgent.prompt.templatePlaceholder": "#### Goal\nDescribe the main purpose and objective of this agent.\n\n #### Skills\n- List the key capabilities\n- And specialized knowledge areas\n\n#### Workflow\n1. Step-by-step process\n2. How the agent should approach tasks\n3. Expected interactions with users\n\n#### Constraints\n- Important limitations to follow\n- Guidelines for behavior",
"settingAgent.prompt.title": "Agent Profile",
"settingAgent.submit": "Update Agent",
"settingAgent.tag.desc": "Agent tags will be displayed in the Agent Community",
+1
View File
@@ -215,6 +215,7 @@
"settingAgent.name.placeholder": "请输入助理名称",
"settingAgent.name.title": "名称",
"settingAgent.prompt.placeholder": "输入助理设定,按 / 打开命令菜单",
"settingAgent.prompt.templatePlaceholder": "#### 目标\n描述助理的主要目的和目标。\n\n#### 技能\n- 列出关键能力\n- 以及专业知识领域\n\n#### 工作流程\n1. 逐步处理流程\n2. 助理应如何处理任务\n3. 与用户的预期交互\n\n#### 约束\n- 需要遵循的重要限制\n- 行为准则",
"settingAgent.prompt.title": "助理设定",
"settingAgent.submit": "更新助理信息",
"settingAgent.tag.desc": "助理标签将在助理社区中展示",
@@ -20,7 +20,7 @@ import { useAgentStore } from '@/store/agent';
import { agentSelectors } from '@/store/agent/selectors';
import { useMentionOptions } from '../ProfileEditor/MentionList';
import PROMPT_TEMPLATE from '../ProfileEditor/promptTemplate.json';
import { EMPTY_EDITOR_STATE } from '../constants';
import { useProfileStore } from '../store';
import TypoBar from './TypoBar';
import { useSlashItems } from './useSlashItems';
@@ -33,7 +33,7 @@ const EditorCanvas = memo(() => {
const editorData = config?.editorData;
const systemRole = config?.systemRole;
const updateConfig = useAgentStore((s) => s.updateAgentConfig);
const [initialLoad] = useState(editorData || PROMPT_TEMPLATE);
const [initialLoad] = useState(editorData || EMPTY_EDITOR_STATE);
const mentionOptions = useMentionOptions();
const editor = useProfileStore((s) => s.editor);
const handleContentChange = useProfileStore((s) => s.handleContentChange);
@@ -90,12 +90,11 @@ const EditorCanvas = memo(() => {
if (streamingInProgress) return;
try {
if (editorData) {
editor.setDocument('json', editorData || PROMPT_TEMPLATE);
editor.setDocument('json', editorData);
} else if (systemRole) {
editor.setDocument('markdown', systemRole);
} else {
editor.setDocument('json', PROMPT_TEMPLATE);
}
// If no editorData and no systemRole, leave editor empty to show placeholder
setContentInit(true);
} catch (error) {
console.error('[EditorCanvas] Failed to init editor content:', error);
@@ -106,7 +105,6 @@ const EditorCanvas = memo(() => {
<div
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Editor
@@ -116,7 +114,7 @@ const EditorCanvas = memo(() => {
mentionOption={mentionOptions}
onInit={() => setEditorInit(true)}
onTextChange={handleChange}
placeholder={t('settingAgent.prompt.placeholder')}
placeholder={t('settingAgent.prompt.templatePlaceholder')}
plugins={[
ReactListPlugin,
ReactCodePlugin,
@@ -2,11 +2,11 @@
import { KLAVIS_SERVER_TYPES, type KlavisServerType } from '@lobechat/const';
import { Avatar, Button, Flexbox, Icon, type ItemType, Segmented } from '@lobehub/ui';
import { cssVar } from 'antd-style';
import { createStaticStyles, cssVar } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { ArrowRight, PlusIcon, Store, ToyBrick } from 'lucide-react';
import Image from 'next/image';
import React, { Suspense, memo, useEffect, useMemo, useRef, useState } from 'react';
import React, { Suspense, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import PluginAvatar from '@/components/Plugins/PluginAvatar';
@@ -17,7 +17,7 @@ import PluginStore from '@/features/PluginStore';
import { useCheckPluginsIsInstalled } from '@/hooks/useCheckPluginsIsInstalled';
import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
import { useAgentStore } from '@/store/agent';
import { agentSelectors } from '@/store/agent/selectors';
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
import { useToolStore } from '@/store/tool';
import {
@@ -28,8 +28,39 @@ import {
import PluginTag from './PluginTag';
const WEB_BROWSING_IDENTIFIER = 'lobe-web-browsing';
type TabType = 'all' | 'installed';
const prefixCls = 'ant';
const styles = createStaticStyles(({ css }) => ({
dropdown: css`
overflow: hidden;
width: 100%;
border: 1px solid ${cssVar.colorBorderSecondary};
border-radius: ${cssVar.borderRadiusLG};
background: ${cssVar.colorBgElevated};
box-shadow: ${cssVar.boxShadowSecondary};
.${prefixCls}-dropdown-menu {
border-radius: 0 !important;
background: transparent !important;
box-shadow: none !important;
}
`,
header: css`
padding: ${cssVar.paddingXS};
border-block-end: 1px solid ${cssVar.colorBorderSecondary};
background: transparent;
`,
scroller: css`
overflow: hidden auto;
`,
}));
/**
* Klavis 服务器图标组件
* 对于 string 类型的 icon,使用 Image 组件渲染
@@ -54,8 +85,12 @@ const AgentTool = memo(() => {
const plugins = config?.plugins || [];
const toggleAgentPlugin = useAgentStore((s) => s.toggleAgentPlugin);
const updateAgentChatConfig = useAgentStore((s) => s.updateAgentChatConfig);
const installedPluginList = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
const builtinList = useToolStore(builtinToolSelectors.metaList, isEqual);
const builtinList = useToolStore(builtinToolSelectors.allMetaList, isEqual);
// Web browsing uses searchMode instead of plugins array
const isSearchEnabled = useAgentStore(agentChatConfigSelectors.isAgentEnableSearch);
// Klavis 相关状态
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
@@ -81,6 +116,35 @@ const AgentTool = memo(() => {
// 使用 SWR 加载用户的 Klavis 集成(从数据库)
useFetchUserKlavisServers(isKlavisEnabledInEnv);
// Toggle web browsing via searchMode
const toggleWebBrowsing = useCallback(async () => {
const nextMode = isSearchEnabled ? 'off' : 'auto';
await updateAgentChatConfig({ searchMode: nextMode });
}, [isSearchEnabled, updateAgentChatConfig]);
// Check if a tool is enabled (handles web browsing specially)
const isToolEnabled = useCallback(
(identifier: string) => {
if (identifier === WEB_BROWSING_IDENTIFIER) {
return isSearchEnabled;
}
return plugins.includes(identifier);
},
[plugins, isSearchEnabled],
);
// Toggle a tool (handles web browsing specially)
const handleToggleTool = useCallback(
async (identifier: string) => {
if (identifier === WEB_BROWSING_IDENTIFIER) {
await toggleWebBrowsing();
} else {
await toggleAgentPlugin(identifier);
}
},
[toggleWebBrowsing, toggleAgentPlugin],
);
// Set default tab based on installed plugins (only on first load)
useEffect(() => {
if (!isInitializedRef.current && plugins.length >= 0) {
@@ -101,11 +165,14 @@ const AgentTool = memo(() => {
);
// 过滤掉 builtinList 中的 klavis 工具(它们会单独显示在 Klavis 区域)
// 同时过滤掉 availableInWeb: false 的工具(如 LocalSystem 仅桌面版可用)
const filteredBuiltinList = useMemo(
() =>
isKlavisEnabledInEnv
? builtinList.filter((item) => !allKlavisTypeIdentifiers.has(item.identifier))
: builtinList,
builtinList
.filter((item) => item.availableInWeb)
.filter((item) =>
isKlavisEnabledInEnv ? !allKlavisTypeIdentifiers.has(item.identifier) : true,
),
[builtinList, allKlavisTypeIdentifiers, isKlavisEnabledInEnv],
);
@@ -132,11 +199,15 @@ const AgentTool = memo(() => {
// Handle plugin remove via Tag close
const handleRemovePlugin =
(pluginId: string | { enabled: boolean; identifier: string; settings: Record<string, any> }) =>
(e: React.MouseEvent) => {
async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const identifier = typeof pluginId === 'string' ? pluginId : pluginId?.identifier;
toggleAgentPlugin(identifier, false);
if (identifier === WEB_BROWSING_IDENTIFIER) {
await updateAgentChatConfig({ searchMode: 'off' });
} else {
toggleAgentPlugin(identifier, false);
}
};
// Build dropdown menu items (adapted from useControls)
@@ -153,12 +224,12 @@ const AgentTool = memo(() => {
key: item.identifier,
label: (
<ToolItem
checked={plugins.includes(item.identifier)}
checked={isToolEnabled(item.identifier)}
id={item.identifier}
label={item.meta?.title}
onUpdate={async () => {
setUpdating(true);
await toggleAgentPlugin(item.identifier);
await handleToggleTool(item.identifier);
setUpdating(false);
}}
/>
@@ -167,7 +238,7 @@ const AgentTool = memo(() => {
// Klavis 服务器
...klavisServerItems,
],
[filteredBuiltinList, klavisServerItems, plugins, toggleAgentPlugin],
[filteredBuiltinList, klavisServerItems, isToolEnabled, handleToggleTool],
);
// Plugin items for dropdown
@@ -242,7 +313,7 @@ const AgentTool = memo(() => {
// 已启用的 builtin 工具
const enabledBuiltinItems = filteredBuiltinList
.filter((item) => plugins.includes(item.identifier))
.filter((item) => isToolEnabled(item.identifier))
.map((item) => ({
icon: <Avatar avatar={item.meta.avatar} size={20} style={{ flex: 'none' }} />,
key: item.identifier,
@@ -253,7 +324,7 @@ const AgentTool = memo(() => {
label={item.meta?.title}
onUpdate={async () => {
setUpdating(true);
await toggleAgentPlugin(item.identifier);
await handleToggleTool(item.identifier);
setUpdating(false);
}}
/>
@@ -311,42 +382,21 @@ const AgentTool = memo(() => {
}
return items;
}, [filteredBuiltinList, klavisServerItems, installedPluginList, plugins, toggleAgentPlugin, t]);
}, [
filteredBuiltinList,
klavisServerItems,
installedPluginList,
plugins,
isToolEnabled,
handleToggleTool,
toggleAgentPlugin,
t,
]);
// Use effective tab for display (default to all while initializing)
const effectiveTab = activeTab ?? 'all';
const currentItems = effectiveTab === 'all' ? allTabItems : installedTabItems;
// Final menu items with tab segmented control
const menuItems: ItemType[] = useMemo(
() => [
{
key: 'tabs',
label: (
<Segmented
block
onChange={(v) => setActiveTab(v as TabType)}
options={[
{
label: t('tools.tabs.all', { defaultValue: 'All' }),
value: 'all',
},
{
label: t('tools.tabs.installed', { defaultValue: 'Installed' }),
value: 'installed',
},
]}
size="small"
value={effectiveTab}
/>
),
type: 'group',
},
...currentItems,
],
[currentItems, effectiveTab, t],
);
const button = (
<Button
icon={PlusIcon}
@@ -359,12 +409,22 @@ const AgentTool = memo(() => {
</Button>
);
// Combine plugins and web browsing for display
const allEnabledTools = useMemo(() => {
const tools = [...plugins];
// Add web browsing if enabled (it's not in plugins array)
if (isSearchEnabled && !tools.includes(WEB_BROWSING_IDENTIFIER)) {
tools.unshift(WEB_BROWSING_IDENTIFIER);
}
return tools;
}, [plugins, isSearchEnabled]);
return (
<>
{/* Plugin Selector and Tags */}
<Flexbox align="center" gap={8} horizontal wrap={'wrap'}>
{/* Second Row: Selected Plugins as Tags */}
{plugins?.map((pluginId) => {
{allEnabledTools.map((pluginId) => {
return (
<PluginTag key={pluginId} onRemove={handleRemovePlugin(pluginId)} pluginId={pluginId} />
);
@@ -375,10 +435,49 @@ const AgentTool = memo(() => {
<ActionDropdown
maxHeight={500}
maxWidth={480}
menu={{ items: menuItems }}
menu={{
items: currentItems,
style: {
// let only the custom scroller scroll
maxHeight: 'unset',
overflowY: 'visible',
},
}}
minHeight={isKlavisEnabledInEnv ? 500 : undefined}
minWidth={320}
placement={'bottomLeft'}
popupRender={(menu) => (
<div className={styles.dropdown}>
{/* stopPropagation prevents dropdown's onClick from calling preventDefault on Segmented */}
<div className={styles.header} onClick={(e) => e.stopPropagation()}>
<Segmented
block
onChange={(v) => setActiveTab(v as TabType)}
options={[
{
label: t('tools.tabs.all', { defaultValue: 'All' }),
value: 'all',
},
{
label: t('tools.tabs.installed', { defaultValue: 'Installed' }),
value: 'installed',
},
]}
size="small"
value={effectiveTab}
/>
</div>
<div
className={styles.scroller}
style={{
maxHeight: 500,
minHeight: isKlavisEnabledInEnv ? 500 : undefined,
}}
>
{menu}
</div>
</div>
)}
trigger={['click']}
>
{button}
@@ -386,8 +485,8 @@ const AgentTool = memo(() => {
</Suspense>
</Flexbox>
{/* PluginStore Modal */}
<PluginStore open={modalOpen} setOpen={setModalOpen} />
{/* PluginStore Modal - rendered outside Flexbox to avoid event interference */}
{modalOpen && <PluginStore open={modalOpen} setOpen={setModalOpen} />}
</>
);
});
@@ -59,8 +59,8 @@ const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
// Extract identifier
const identifier = typeof pluginId === 'string' ? pluginId : pluginId?.identifier;
// Get local plugin lists
const builtinList = useToolStore(builtinToolSelectors.metaList, isEqual);
// Get local plugin lists (use allMetaList to include hidden tools)
const builtinList = useToolStore(builtinToolSelectors.allMetaList, isEqual);
const installedPluginList = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
// Klavis 相关状态
@@ -79,6 +79,7 @@ const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
// Check if this Klavis server is connected
const connectedServer = allKlavisServers.find((s) => s.identifier === identifier);
return {
availableInWeb: true,
icon: klavisType.icon,
isInstalled: !!connectedServer,
label: klavisType.label,
@@ -91,6 +92,7 @@ const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
const builtinMeta = builtinList.find((p) => p.identifier === identifier);
if (builtinMeta) {
return {
availableInWeb: builtinMeta.availableInWeb,
avatar: builtinMeta.meta.avatar,
isInstalled: true,
title: builtinMeta.meta.title,
@@ -101,6 +103,7 @@ const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
const installedMeta = installedPluginList.find((p) => p.identifier === identifier);
if (installedMeta) {
return {
availableInWeb: true,
avatar: installedMeta.avatar,
isInstalled: true,
title: installedMeta.title,
@@ -120,6 +123,7 @@ const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
// Determine final metadata
const meta = localMeta || {
availableInWeb: true,
avatar: remoteData?.avatar,
isInstalled: false,
title: remoteData?.title || identifier,
@@ -127,6 +131,7 @@ const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
};
const displayTitle = isLoading ? 'Loading...' : meta.title;
const isDesktopOnly = !meta.availableInWeb;
// Render icon based on type
const renderIcon = () => {
@@ -152,6 +157,18 @@ const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
return null;
};
// Build display text
const getDisplayText = () => {
let text = displayTitle;
if (isDesktopOnly) {
text += ` (${t('tools.desktopOnly', { defaultValue: 'Desktop Only' })})`;
}
if (!meta.isInstalled) {
text += ` (${t('tools.notInstalled', { defaultValue: 'Not Installed' })})`;
}
return text;
};
return (
<Tag
className={styles.tag}
@@ -167,9 +184,7 @@ const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
}
variant={isDarkMode ? 'filled' : 'outlined'}
>
{!meta.isInstalled
? `${displayTitle} (${t('tools.notInstalled', { defaultValue: 'Not Installed' })})`
: displayTitle}
{getDisplayText()}
</Tag>
);
});
@@ -31,7 +31,6 @@ const ProfileEditor = memo(() => {
<Flexbox
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
style={{ cursor: 'default', marginBottom: 12 }}
>
@@ -1,277 +0,0 @@
{
"root": {
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Goal",
"type": "text",
"version": 1
}
],
"direction": null,
"format": "",
"indent": 0,
"type": "heading",
"version": 1,
"tag": "h4"
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Describe the main purpose and objective of this agent.",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "paragraph",
"version": 1,
"textFormat": 0,
"textStyle": ""
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Skills",
"type": "text",
"version": 1
}
],
"direction": null,
"format": "",
"indent": 0,
"type": "heading",
"version": 1,
"tag": "h4"
},
{
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "List the key capabilities",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 1
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "And specialized knowledge areas",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 2
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "list",
"version": 1,
"listType": "bullet",
"start": 1,
"tag": "ul"
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Workflow",
"type": "text",
"version": 1
}
],
"direction": null,
"format": "",
"indent": 0,
"type": "heading",
"version": 1,
"tag": "h4"
},
{
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Step-by-step process",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 1
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "How the agent should approach tasks",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 2
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Expected interactions with users",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 3
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "list",
"version": 1,
"listType": "number",
"start": 1,
"tag": "ol"
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Constraints",
"type": "text",
"version": 1
}
],
"direction": null,
"format": "",
"indent": 0,
"type": "heading",
"version": 1,
"tag": "h4"
},
{
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Important limitations to follow",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 1
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Guidelines for behavior",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 2
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "list",
"version": 1,
"listType": "bullet",
"start": 1,
"tag": "ul"
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "root",
"version": 1,
"textStyle": "--shiki-dark:var(--color-info);--shiki-light:var(--color-info)"
}
}
@@ -0,0 +1,36 @@
/**
* Empty editor state for initializing the editor
* This is the minimal JSON structure required by the editor
* Must have at least one paragraph with an empty text node
*/
export const EMPTY_EDITOR_STATE = {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '',
type: 'text',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
textFormat: 0,
textStyle: '',
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'root',
version: 1,
},
};
@@ -20,7 +20,7 @@ import { useAgentGroupStore } from '@/store/agentGroup';
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
import { useMentionOptions } from '../ProfileEditor/MentionList';
import PROMPT_TEMPLATE from '../ProfileEditor/promptTemplate.json';
import { EMPTY_EDITOR_STATE } from '../constants';
import { useProfileStore } from '../store';
import TypoBar from './TypoBar';
import { useSlashItems } from './useSlashItems';
@@ -41,7 +41,7 @@ const EditorCanvas = memo(() => {
// For group profile, we don't use editorData - just systemPrompt
const editorData = undefined;
const [initialLoad] = useState(PROMPT_TEMPLATE);
const [initialLoad] = useState(EMPTY_EDITOR_STATE);
const mentionOptions = useMentionOptions();
const editor = useProfileStore((s) => s.editor);
const handleContentChange = useProfileStore((s) => s.handleContentChange);
@@ -86,12 +86,11 @@ const EditorCanvas = memo(() => {
if (streamingInProgress) return;
try {
if (editorData) {
editor.setDocument('json', editorData || PROMPT_TEMPLATE);
editor.setDocument('json', editorData);
} else if (systemRole) {
editor.setDocument('markdown', systemRole);
} else {
editor.setDocument('json', PROMPT_TEMPLATE);
}
// If no editorData and no systemRole, leave editor empty to show placeholder
setContentInit(true);
} catch (error) {
console.error('[EditorCanvas] Failed to init editor content:', error);
@@ -102,7 +101,6 @@ const EditorCanvas = memo(() => {
<div
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Editor
@@ -112,7 +110,7 @@ const EditorCanvas = memo(() => {
mentionOption={mentionOptions}
onInit={() => setEditorInit(true)}
onTextChange={handleChange}
placeholder={t('settingAgent.prompt.placeholder')}
placeholder={t('settingAgent.prompt.templatePlaceholder')}
plugins={[
ReactListPlugin,
ReactCodePlugin,
@@ -31,7 +31,6 @@ const ProfileEditor = memo(() => {
<Flexbox
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
style={{ cursor: 'default', marginBottom: 12 }}
>
@@ -48,7 +47,7 @@ const ProfileEditor = memo(() => {
<ModelSelect
onChange={updateConfig}
value={{
model: config?.model,
model: config?.model,
provider: config?.provider,
}}
/>
@@ -1,277 +0,0 @@
{
"root": {
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Goal",
"type": "text",
"version": 1
}
],
"direction": null,
"format": "",
"indent": 0,
"type": "heading",
"version": 1,
"tag": "h4"
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Describe the main purpose and objective of this agent.",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "paragraph",
"version": 1,
"textFormat": 0,
"textStyle": ""
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Skills",
"type": "text",
"version": 1
}
],
"direction": null,
"format": "",
"indent": 0,
"type": "heading",
"version": 1,
"tag": "h4"
},
{
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "List the key capabilities",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 1
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "And specialized knowledge areas",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 2
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "list",
"version": 1,
"listType": "bullet",
"start": 1,
"tag": "ul"
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Workflow",
"type": "text",
"version": 1
}
],
"direction": null,
"format": "",
"indent": 0,
"type": "heading",
"version": 1,
"tag": "h4"
},
{
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Step-by-step process",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 1
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "How the agent should approach tasks",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 2
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Expected interactions with users",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 3
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "list",
"version": 1,
"listType": "number",
"start": 1,
"tag": "ol"
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Constraints",
"type": "text",
"version": 1
}
],
"direction": null,
"format": "",
"indent": 0,
"type": "heading",
"version": 1,
"tag": "h4"
},
{
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Important limitations to follow",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 1
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "Guidelines for behavior",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 2
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "list",
"version": 1,
"listType": "bullet",
"start": 1,
"tag": "ul"
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "root",
"version": 1,
"textStyle": "--shiki-dark:var(--color-info);--shiki-light:var(--color-info)"
}
}
@@ -0,0 +1,36 @@
/**
* Empty editor state for initializing the editor
* This is the minimal JSON structure required by the editor
* Must have at least one paragraph with an empty text node
*/
export const EMPTY_EDITOR_STATE = {
root: {
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: '',
type: 'text',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
textFormat: 0,
textStyle: '',
type: 'paragraph',
version: 1,
},
],
direction: null,
format: '',
indent: 0,
type: 'root',
version: 1,
},
};
+13
View File
@@ -237,6 +237,19 @@ export default {
'settingAgent.name.placeholder': 'Enter agent name',
'settingAgent.name.title': 'Name',
'settingAgent.prompt.placeholder': 'Enter agent settings, press / to open the command menu',
'settingAgent.prompt.templatePlaceholder':
'#### Goal\n' +
'Describe the main purpose and objective of this agent.\n\n' +
'#### Skills\n' +
'- List the key capabilities\n' +
'- And specialized knowledge areas\n\n' +
'#### Workflow\n' +
'1. Step-by-step process\n' +
'2. How the agent should approach tasks\n' +
'3. Expected interactions with users\n\n' +
'#### Constraints\n' +
'- Important limitations to follow\n' +
'- Guidelines for behavior',
'settingAgent.prompt.title': 'Agent Profile',
'settingAgent.submit': 'Update Agent',
'settingAgent.tag.desc': 'Agent tags will be displayed in the Agent Community',
+4 -1
View File
@@ -1,4 +1,7 @@
export { builtinToolSelectors } from '../slices/builtin/selectors';
export {
builtinToolSelectors,
type LobeToolMetaWithAvailability,
} from '../slices/builtin/selectors';
export { customPluginSelectors } from '../slices/customPlugin/selectors';
export { klavisStoreSelectors } from '../slices/klavisStore/selectors';
export { mcpStoreSelectors } from '../slices/mcpStore/selectors';
+68 -19
View File
@@ -5,27 +5,30 @@ import { shouldEnableTool } from '@/helpers/toolFilters';
import type { ToolStoreState } from '../../initialState';
import { KlavisServerStatus } from '../klavisStore';
const metaList = (s: ToolStoreState): LobeToolMeta[] => {
// Get builtin tools meta list
const builtinMetas = s.builtinTools
.filter((item) => {
// Filter hidden tools
if (item.hidden) return false;
export interface LobeToolMetaWithAvailability extends LobeToolMeta {
/**
* Whether the tool is available in web environment
* e.g., LocalSystem is desktop-only, so availableInWeb is false
*/
availableInWeb: boolean;
}
// Filter platform-specific tools (e.g., LocalSystem desktop-only)
if (!shouldEnableTool(item.identifier)) return false;
const toBuiltinMeta = (t: ToolStoreState['builtinTools'][number]): LobeToolMeta => ({
author: 'LobeHub',
identifier: t.identifier,
meta: t.manifest.meta,
type: 'builtin' as const,
});
return true;
})
.map((t) => ({
author: 'LobeHub',
identifier: t.identifier,
meta: t.manifest.meta,
type: 'builtin' as const,
}));
const toBuiltinMetaWithAvailability = (
t: ToolStoreState['builtinTools'][number],
): LobeToolMetaWithAvailability => ({
...toBuiltinMeta(t),
availableInWeb: shouldEnableTool(t.identifier),
});
// Get Klavis servers as builtin tools meta
const klavisMetas = (s.servers || [])
const getKlavisMetas = (s: ToolStoreState): LobeToolMeta[] =>
(s.servers || [])
.filter((server) => server.status === KlavisServerStatus.CONNECTED && server.tools?.length)
.map((server) => ({
author: 'Klavis',
@@ -41,9 +44,55 @@ const metaList = (s: ToolStoreState): LobeToolMeta[] => {
type: 'builtin' as const,
}));
return [...builtinMetas, ...klavisMetas];
const getKlavisMetasWithAvailability = (s: ToolStoreState): LobeToolMetaWithAvailability[] =>
getKlavisMetas(s).map((meta) => ({ ...meta, availableInWeb: true }));
/**
* Get visible builtin tools meta list (excludes hidden tools)
* Used for general tool display in chat input bar
*/
const metaList = (s: ToolStoreState): LobeToolMeta[] => {
const builtinMetas = s.builtinTools
.filter((item) => {
// Filter hidden tools
if (item.hidden) return false;
// Filter platform-specific tools (e.g., LocalSystem desktop-only)
if (!shouldEnableTool(item.identifier)) return false;
return true;
})
.map(toBuiltinMeta);
return [...builtinMetas, ...getKlavisMetas(s)];
};
// Tools that should never be exposed in agent profile configuration
const EXCLUDED_TOOLS = new Set([
'lobe-agent-builder',
'lobe-group-agent-builder',
'lobe-group-management',
]);
/**
* Get all builtin tools meta list (includes hidden tools and platform-specific tools)
* Used for agent profile tool configuration where all tools should be configurable
* Returns availability info so UI can show hints for unavailable tools
*/
const allMetaList = (s: ToolStoreState): LobeToolMetaWithAvailability[] => {
const builtinMetas = s.builtinTools
.filter((item) => {
// Exclude internal tools that should not be user-configurable
if (EXCLUDED_TOOLS.has(item.identifier)) return false;
return true;
})
.map(toBuiltinMetaWithAvailability);
return [...builtinMetas, ...getKlavisMetasWithAvailability(s)];
};
export const builtinToolSelectors = {
allMetaList,
metaList,
};
-2
View File
@@ -73,13 +73,11 @@ export const builtinTools: LobeBuiltinTool[] = [
type: 'builtin',
},
{
hidden: true,
identifier: GTDManifest.identifier,
manifest: GTDManifest,
type: 'builtin',
},
{
hidden: true,
identifier: NotebookManifest.identifier,
manifest: NotebookManifest,
type: 'builtin',