mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
✨ feat: improve group profile builder (#11452)
* improve group topic usage
update agent group builder
update to v267
update
update to use createAgentOnly
fix to remove activeId
💄 style: update inspector styles
refactor implement for agent builder and group builder
update style
* improve group profile mode
* fix editor canvas EditorData Mode
* move store to groupProfileStore
* update group profile design
* update test
* fix topic switch issue
* update all
* update tests
This commit is contained in:
@@ -71,11 +71,18 @@
|
||||
"group.desc": "Move a task forward with multiple Agents in one shared space.",
|
||||
"group.memberTooltip": "There are {{count}} members in the group",
|
||||
"group.orchestratorThinking": "Orchestrator is thinking...",
|
||||
"group.profile.contentPlaceholder": "Set the group objectives/work modes here. This information will be shared with all group members.",
|
||||
"group.profile.external": "External",
|
||||
"group.profile.externalAgentWarning": "External agent - changes will sync globally",
|
||||
"group.profile.groupSettings": "Group Settings",
|
||||
"group.profile.supervisor": "Supervisor",
|
||||
"group.profile.supervisorPlaceholder": "The supervisor coordinates different agents. Setting supervisor information here enables more precise workflow coordination.",
|
||||
"group.removeMember": "Remove Member",
|
||||
"group.title": "Group",
|
||||
"groupDescription": "Group description",
|
||||
"groupSidebar.agentProfile.chat": "Chat",
|
||||
"groupSidebar.agentProfile.model": "Model",
|
||||
"groupSidebar.agentProfile.settings": "Settings",
|
||||
"groupSidebar.members.addMember": "Add Member",
|
||||
"groupSidebar.members.enableOrchestrator": "Enable Orchestrator",
|
||||
"groupSidebar.members.memberSettings": "Member Settings",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"builtins.lobe-agent-builder.apiName.getConfig": "Get config",
|
||||
"builtins.lobe-agent-builder.apiName.getMeta": "Get metadata",
|
||||
"builtins.lobe-agent-builder.apiName.getPrompt": "Get system prompt",
|
||||
"builtins.lobe-agent-builder.apiName.installPlugin": "Install Skill",
|
||||
"builtins.lobe-agent-builder.apiName.searchMarketTools": "Search Skill market",
|
||||
"builtins.lobe-agent-builder.apiName.searchOfficialTools": "Search official Skills",
|
||||
"builtins.lobe-agent-builder.apiName.setModel": "Set model",
|
||||
@@ -15,6 +16,12 @@
|
||||
"builtins.lobe-agent-builder.apiName.updateConfig": "Update config",
|
||||
"builtins.lobe-agent-builder.apiName.updateMeta": "Update metadata",
|
||||
"builtins.lobe-agent-builder.apiName.updatePrompt": "Update system prompt",
|
||||
"builtins.lobe-agent-builder.inspector.chars": " chars",
|
||||
"builtins.lobe-agent-builder.inspector.disablePlugin": "Disable",
|
||||
"builtins.lobe-agent-builder.inspector.enablePlugin": "Enable",
|
||||
"builtins.lobe-agent-builder.inspector.modelsCount": " and {{count}} more",
|
||||
"builtins.lobe-agent-builder.inspector.noResults": "No results",
|
||||
"builtins.lobe-agent-builder.inspector.togglePlugin": "Toggle",
|
||||
"builtins.lobe-agent-builder.title": "Agent Builder Expert",
|
||||
"builtins.lobe-cloud-sandbox.apiName.editLocalFile": "Edit file",
|
||||
"builtins.lobe-cloud-sandbox.apiName.executeCode": "Execute code",
|
||||
@@ -31,13 +38,27 @@
|
||||
"builtins.lobe-cloud-sandbox.apiName.searchLocalFiles": "Search files",
|
||||
"builtins.lobe-cloud-sandbox.apiName.writeLocalFile": "Write file",
|
||||
"builtins.lobe-cloud-sandbox.title": "Cloud Sandbox",
|
||||
"builtins.lobe-group-agent-builder.apiName.createAgent": "Create Agent",
|
||||
"builtins.lobe-group-agent-builder.apiName.getAvailableModels": "Get available models",
|
||||
"builtins.lobe-group-agent-builder.apiName.installPlugin": "Install Skill",
|
||||
"builtins.lobe-group-agent-builder.apiName.inviteAgent": "Invite member",
|
||||
"builtins.lobe-group-agent-builder.apiName.removeAgent": "Remove member",
|
||||
"builtins.lobe-group-agent-builder.apiName.searchAgent": "Search Agents",
|
||||
"builtins.lobe-group-agent-builder.apiName.searchMarketTools": "Search Skill market",
|
||||
"builtins.lobe-group-agent-builder.apiName.batchCreateAgents": "Batch create agents",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateAgentConfig": "Update agent config",
|
||||
"builtins.lobe-group-agent-builder.apiName.updatePrompt": "Update system prompt",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateAgentPrompt": "Update agent prompt",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateSupervisorPrompt": "Update supervisor prompt",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateGroup": "Update group",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateGroupPrompt": "Update group prompt",
|
||||
"builtins.lobe-group-agent-builder.inspector.agents": "agents",
|
||||
"builtins.lobe-group-agent-builder.inspector.avatar": "Avatar",
|
||||
"builtins.lobe-group-agent-builder.inspector.backgroundColor": "Background color",
|
||||
"builtins.lobe-group-agent-builder.inspector.description": "Description",
|
||||
"builtins.lobe-group-agent-builder.inspector.noResults": "No results",
|
||||
"builtins.lobe-group-agent-builder.inspector.openingMessage": "Opening message",
|
||||
"builtins.lobe-group-agent-builder.inspector.openingQuestions": "Opening questions",
|
||||
"builtins.lobe-group-agent-builder.inspector.title": "Title",
|
||||
"builtins.lobe-group-agent-builder.title": "Group Builder Expert",
|
||||
"builtins.lobe-group-management.apiName.broadcast": "All speak",
|
||||
"builtins.lobe-group-management.apiName.createAgent": "Add group member",
|
||||
|
||||
@@ -71,11 +71,18 @@
|
||||
"group.desc": "在同一对话空间,让多个助理一起推进任务",
|
||||
"group.memberTooltip": "群组内有 {{count}} 名成员",
|
||||
"group.orchestratorThinking": "主持人思考中…",
|
||||
"group.profile.external": "外部",
|
||||
"group.profile.contentPlaceholder": "在此设定群组的目标/工作模式等,这些信息将会共享给所有群组成员",
|
||||
"group.profile.externalAgentWarning": "外部助理的配置修改将全局同步生效",
|
||||
"group.profile.groupSettings": "群组设定",
|
||||
"group.profile.supervisor": "群组主管",
|
||||
"group.profile.supervisorPlaceholder": "群组主管负责协调不同 Agent,在此设定主管的信息可以使得协调工作流更加精准",
|
||||
"group.removeMember": "移除成员",
|
||||
"group.title": "群组",
|
||||
"groupDescription": "群组描述",
|
||||
"groupSidebar.agentProfile.chat": "对话",
|
||||
"groupSidebar.agentProfile.model": "模型",
|
||||
"groupSidebar.agentProfile.settings": "设置",
|
||||
"groupSidebar.members.addMember": "添加成员",
|
||||
"groupSidebar.members.enableOrchestrator": "启用主持人",
|
||||
"groupSidebar.members.memberSettings": "成员设置",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"builtins.lobe-agent-builder.apiName.getConfig": "获取配置",
|
||||
"builtins.lobe-agent-builder.apiName.getMeta": "获取元数据",
|
||||
"builtins.lobe-agent-builder.apiName.getPrompt": "获取系统提示词",
|
||||
"builtins.lobe-agent-builder.apiName.installPlugin": "安装技能",
|
||||
"builtins.lobe-agent-builder.apiName.searchMarketTools": "搜索技能市场",
|
||||
"builtins.lobe-agent-builder.apiName.searchOfficialTools": "搜索官方技能",
|
||||
"builtins.lobe-agent-builder.apiName.setModel": "设置模型",
|
||||
@@ -15,6 +16,12 @@
|
||||
"builtins.lobe-agent-builder.apiName.updateConfig": "更新配置",
|
||||
"builtins.lobe-agent-builder.apiName.updateMeta": "更新元数据",
|
||||
"builtins.lobe-agent-builder.apiName.updatePrompt": "更新系统提示词",
|
||||
"builtins.lobe-agent-builder.inspector.chars": "字",
|
||||
"builtins.lobe-agent-builder.inspector.disablePlugin": "关闭",
|
||||
"builtins.lobe-agent-builder.inspector.enablePlugin": "开启",
|
||||
"builtins.lobe-agent-builder.inspector.modelsCount": " 等 {{count}} 个模型",
|
||||
"builtins.lobe-agent-builder.inspector.noResults": "无结果",
|
||||
"builtins.lobe-agent-builder.inspector.togglePlugin": "切换",
|
||||
"builtins.lobe-agent-builder.title": "助理构建专家",
|
||||
"builtins.lobe-cloud-sandbox.apiName.editLocalFile": "编辑文件",
|
||||
"builtins.lobe-cloud-sandbox.apiName.executeCode": "执行代码",
|
||||
@@ -31,13 +38,27 @@
|
||||
"builtins.lobe-cloud-sandbox.apiName.searchLocalFiles": "搜索文件",
|
||||
"builtins.lobe-cloud-sandbox.apiName.writeLocalFile": "写入文件",
|
||||
"builtins.lobe-cloud-sandbox.title": "云端沙盒",
|
||||
"builtins.lobe-group-agent-builder.apiName.createAgent": "创建 Agent",
|
||||
"builtins.lobe-group-agent-builder.apiName.getAvailableModels": "获取可用模型",
|
||||
"builtins.lobe-group-agent-builder.apiName.installPlugin": "安装技能",
|
||||
"builtins.lobe-group-agent-builder.apiName.inviteAgent": "邀请成员",
|
||||
"builtins.lobe-group-agent-builder.apiName.removeAgent": "移除成员",
|
||||
"builtins.lobe-group-agent-builder.apiName.searchAgent": "搜索 Agent",
|
||||
"builtins.lobe-group-agent-builder.apiName.searchMarketTools": "搜索技能市场",
|
||||
"builtins.lobe-group-agent-builder.apiName.batchCreateAgents": "批量创建 Agent",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateAgentConfig": "更新代理配置",
|
||||
"builtins.lobe-group-agent-builder.apiName.updatePrompt": "更新系统提示词",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateAgentPrompt": "更新助理提示词",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateSupervisorPrompt": "更新群组主管提示词",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateGroup": "更新群组",
|
||||
"builtins.lobe-group-agent-builder.apiName.updateGroupPrompt": "更新群组提示词",
|
||||
"builtins.lobe-group-agent-builder.inspector.agents": "个 Agent",
|
||||
"builtins.lobe-group-agent-builder.inspector.avatar": "头像",
|
||||
"builtins.lobe-group-agent-builder.inspector.backgroundColor": "背景色",
|
||||
"builtins.lobe-group-agent-builder.inspector.description": "描述",
|
||||
"builtins.lobe-group-agent-builder.inspector.noResults": "无结果",
|
||||
"builtins.lobe-group-agent-builder.inspector.openingMessage": "开场白",
|
||||
"builtins.lobe-group-agent-builder.inspector.openingQuestions": "开场问题",
|
||||
"builtins.lobe-group-agent-builder.inspector.title": "标题",
|
||||
"builtins.lobe-group-agent-builder.title": "群组构建专家",
|
||||
"builtins.lobe-group-management.apiName.broadcast": "所有人发言",
|
||||
"builtins.lobe-group-management.apiName.createAgent": "添加群组成员",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./client": "./src/client/index.ts",
|
||||
"./executor": "./src/executor.ts",
|
||||
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { cx } from 'antd-style';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { GetAvailableModelsParams, GetAvailableModelsState } from '../../../types';
|
||||
|
||||
export const GetAvailableModelsInspector = memo<
|
||||
BuiltinInspectorProps<GetAvailableModelsParams, GetAvailableModelsState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const providerId = args?.providerId || partialArgs?.providerId;
|
||||
|
||||
// Calculate total model count from providers
|
||||
const modelInfo = useMemo(() => {
|
||||
if (!pluginState?.providers) return null;
|
||||
|
||||
const allModels = pluginState.providers.flatMap((p) => p.models);
|
||||
const totalCount = allModels.length;
|
||||
|
||||
if (totalCount === 0) return null;
|
||||
|
||||
// Get first 2 model names for display
|
||||
const displayModels = allModels.slice(0, 2).map((m) => m.name || m.id);
|
||||
return { displayModels, totalCount };
|
||||
}, [pluginState?.providers]);
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming || isLoading) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.getAvailableModels')}</span>
|
||||
{providerId && (
|
||||
<>
|
||||
: <span className={highlightTextStyles.primary}>{providerId}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Loaded state with results
|
||||
return (
|
||||
<div className={inspectorTextStyles.root}>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.getAvailableModels')}: </span>
|
||||
{modelInfo && (
|
||||
<span className={highlightTextStyles.primary}>
|
||||
{modelInfo.displayModels.join(' / ')}
|
||||
{modelInfo.totalCount > 2 &&
|
||||
t('builtins.lobe-agent-builder.inspector.modelsCount', {
|
||||
count: modelInfo.totalCount,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
GetAvailableModelsInspector.displayName = 'GetAvailableModelsInspector';
|
||||
|
||||
export default GetAvailableModelsInspector;
|
||||
@@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { InstallPluginParams, InstallPluginState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const InstallPluginInspector = memo<
|
||||
BuiltinInspectorProps<InstallPluginParams, InstallPluginState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const identifier = args?.identifier || partialArgs?.identifier;
|
||||
const displayName = pluginState?.pluginName || identifier;
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !identifier) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.installPlugin')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Get installation result
|
||||
const isSuccess = pluginState?.success && pluginState?.installed;
|
||||
const hasResult = pluginState?.success !== undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.installPlugin')}: </span>
|
||||
{displayName && <span className={highlightTextStyles.primary}>{displayName}</span>}
|
||||
{!isLoading &&
|
||||
hasResult &&
|
||||
(isSuccess ? (
|
||||
<Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
|
||||
) : (
|
||||
<X className={styles.statusIcon} color={cssVar.colorError} size={14} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
InstallPluginInspector.displayName = 'InstallPluginInspector';
|
||||
|
||||
export default InstallPluginInspector;
|
||||
@@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import { cssVar, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { SearchMarketToolsParams, SearchMarketToolsState } from '../../../types';
|
||||
|
||||
export const SearchMarketToolsInspector = memo<
|
||||
BuiltinInspectorProps<SearchMarketToolsParams, SearchMarketToolsState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const query = args?.query || partialArgs?.query;
|
||||
const category = args?.category || partialArgs?.category;
|
||||
const displayText = query || category;
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !displayText) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.searchMarketTools')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const resultCount = pluginState?.tools?.length ?? 0;
|
||||
const hasResults = resultCount > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.searchMarketTools')}: </span>
|
||||
{displayText && <span className={highlightTextStyles.primary}>{displayText}</span>}
|
||||
{!isLoading &&
|
||||
!isArgumentsStreaming &&
|
||||
pluginState?.tools &&
|
||||
(hasResults ? (
|
||||
<span style={{ marginInlineStart: 4 }}>({resultCount})</span>
|
||||
) : (
|
||||
<Text
|
||||
as={'span'}
|
||||
color={cssVar.colorTextDescription}
|
||||
fontSize={12}
|
||||
style={{ marginInlineStart: 4 }}
|
||||
>
|
||||
({t('builtins.lobe-agent-builder.inspector.noResults')})
|
||||
</Text>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SearchMarketToolsInspector.displayName = 'SearchMarketToolsInspector';
|
||||
|
||||
export default SearchMarketToolsInspector;
|
||||
@@ -0,0 +1,94 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { Check } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { UpdateAgentConfigParams, UpdateConfigState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdateConfigInspector = memo<
|
||||
BuiltinInspectorProps<UpdateAgentConfigParams, UpdateConfigState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const togglePlugin = args?.togglePlugin || partialArgs?.togglePlugin;
|
||||
const config = args?.config || partialArgs?.config;
|
||||
const meta = args?.meta || partialArgs?.meta;
|
||||
|
||||
// Build display text
|
||||
const displayText = useMemo(() => {
|
||||
// If toggling plugin, show that info
|
||||
if (togglePlugin?.pluginId) {
|
||||
const enabled = togglePlugin.enabled ?? pluginState?.togglePlugin?.enabled;
|
||||
const action =
|
||||
enabled === true
|
||||
? t('builtins.lobe-agent-builder.inspector.enablePlugin')
|
||||
: enabled === false
|
||||
? t('builtins.lobe-agent-builder.inspector.disablePlugin')
|
||||
: t('builtins.lobe-agent-builder.inspector.togglePlugin');
|
||||
return `${action} ${togglePlugin.pluginId}`;
|
||||
}
|
||||
|
||||
// Otherwise show updated fields
|
||||
const fields: string[] = [];
|
||||
if (config) {
|
||||
if (config.model) fields.push('model');
|
||||
if (config.provider) fields.push('provider');
|
||||
if (config.plugins) fields.push('plugins');
|
||||
if (config.params) fields.push('params');
|
||||
if (config.chatConfig) fields.push('chatConfig');
|
||||
}
|
||||
if (meta) {
|
||||
if (meta.title) fields.push('title');
|
||||
if (meta.description) fields.push('description');
|
||||
if (meta.avatar) fields.push('avatar');
|
||||
}
|
||||
|
||||
return fields.length > 0 ? fields.join(', ') : '';
|
||||
}, [togglePlugin, config, meta, pluginState, t]);
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !displayText) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.updateConfig')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isSuccess = pluginState?.success;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.updateConfig')}</span>
|
||||
{displayText && (
|
||||
<>
|
||||
: <span className={highlightTextStyles.primary}>{displayText}</span>
|
||||
</>
|
||||
)}
|
||||
{!isLoading && isSuccess && (
|
||||
<Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
UpdateConfigInspector.displayName = 'UpdateConfigInspector';
|
||||
|
||||
export default UpdateConfigInspector;
|
||||
@@ -0,0 +1,96 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { Check } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { UpdatePromptParams, UpdatePromptState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdatePromptInspector = memo<
|
||||
BuiltinInspectorProps<UpdatePromptParams, UpdatePromptState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const prompt = args?.prompt || partialArgs?.prompt;
|
||||
|
||||
// Calculate length difference
|
||||
const lengthDiff = useMemo(() => {
|
||||
if (!pluginState) return null;
|
||||
|
||||
const newLength = pluginState.newPrompt?.length ?? 0;
|
||||
const prevLength = pluginState.previousPrompt?.length ?? 0;
|
||||
const diff = newLength - prevLength;
|
||||
|
||||
return diff;
|
||||
}, [pluginState]);
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !prompt) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.updatePrompt')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate streaming length change
|
||||
const streamingLength = prompt?.length ?? 0;
|
||||
const isSuccess = pluginState?.success;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-agent-builder.apiName.updatePrompt')}</span>
|
||||
{/* Show length diff when completed */}
|
||||
{!isLoading && !isArgumentsStreaming && lengthDiff !== null && (
|
||||
<Text
|
||||
as={'span'}
|
||||
code
|
||||
color={lengthDiff >= 0 ? cssVar.colorSuccess : cssVar.colorError}
|
||||
fontSize={12}
|
||||
style={{ marginInlineStart: 4 }}
|
||||
>
|
||||
({lengthDiff >= 0 ? '+' : ''}
|
||||
{lengthDiff}
|
||||
{t('builtins.lobe-agent-builder.inspector.chars')})
|
||||
</Text>
|
||||
)}
|
||||
{/* Show streaming length */}
|
||||
{(isArgumentsStreaming || isLoading) && streamingLength > 0 && (
|
||||
<Text
|
||||
as={'span'}
|
||||
code
|
||||
color={cssVar.colorTextDescription}
|
||||
fontSize={12}
|
||||
style={{ marginInlineStart: 4 }}
|
||||
>
|
||||
({streamingLength}
|
||||
{t('builtins.lobe-agent-builder.inspector.chars')})
|
||||
</Text>
|
||||
)}
|
||||
{!isLoading && !isArgumentsStreaming && isSuccess && (
|
||||
<Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
UpdatePromptInspector.displayName = 'UpdatePromptInspector';
|
||||
|
||||
export default UpdatePromptInspector;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { type BuiltinInspector } from '@lobechat/types';
|
||||
|
||||
import { AgentBuilderApiName } from '../../types';
|
||||
import { GetAvailableModelsInspector } from './GetAvailableModels';
|
||||
import { InstallPluginInspector } from './InstallPlugin';
|
||||
import { SearchMarketToolsInspector } from './SearchMarketTools';
|
||||
import { UpdateConfigInspector } from './UpdateConfig';
|
||||
import { UpdatePromptInspector } from './UpdatePrompt';
|
||||
|
||||
/**
|
||||
* Agent Builder Inspector Components Registry
|
||||
*
|
||||
* Inspector components customize the title/header area
|
||||
* of tool calls in the conversation UI.
|
||||
*/
|
||||
export const AgentBuilderInspectors: Record<string, BuiltinInspector> = {
|
||||
[AgentBuilderApiName.getAvailableModels]: GetAvailableModelsInspector as BuiltinInspector,
|
||||
[AgentBuilderApiName.installPlugin]: InstallPluginInspector as BuiltinInspector,
|
||||
[AgentBuilderApiName.searchMarketTools]: SearchMarketToolsInspector as BuiltinInspector,
|
||||
[AgentBuilderApiName.updateAgentConfig]: UpdateConfigInspector as BuiltinInspector,
|
||||
[AgentBuilderApiName.updatePrompt]: UpdatePromptInspector as BuiltinInspector,
|
||||
};
|
||||
|
||||
// Re-export individual inspectors for reuse in group-agent-builder
|
||||
export { GetAvailableModelsInspector } from './GetAvailableModels';
|
||||
export { InstallPluginInspector } from './InstallPlugin';
|
||||
export { SearchMarketToolsInspector } from './SearchMarketTools';
|
||||
export { UpdateConfigInspector } from './UpdateConfig';
|
||||
export { UpdatePromptInspector } from './UpdatePrompt';
|
||||
@@ -1,4 +1,17 @@
|
||||
// Inspector components (customized tool call headers)
|
||||
export { AgentBuilderInspectors } from './Inspector';
|
||||
export {
|
||||
GetAvailableModelsInspector,
|
||||
InstallPluginInspector,
|
||||
SearchMarketToolsInspector,
|
||||
UpdateConfigInspector,
|
||||
UpdatePromptInspector,
|
||||
} from './Inspector';
|
||||
|
||||
// Intervention components (interactive editing)
|
||||
export { AgentBuilderInterventions } from './Intervention';
|
||||
|
||||
// Render components (read-only snapshots)
|
||||
export { AgentBuilderRenders } from './Render';
|
||||
|
||||
// Re-export types and manifest for convenience
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Agent Builder Executor
|
||||
*
|
||||
* Handles all agent builder tool calls for configuring and customizing agents.
|
||||
*/
|
||||
import { BaseExecutor, type BuiltinToolContext, type BuiltinToolResult } from '@lobechat/types';
|
||||
|
||||
import { AgentBuilderExecutionRuntime } from './ExecutionRuntime';
|
||||
import {
|
||||
AgentBuilderApiName,
|
||||
AgentBuilderIdentifier,
|
||||
type GetAvailableModelsParams,
|
||||
type InstallPluginParams,
|
||||
type SearchMarketToolsParams,
|
||||
type UpdateAgentConfigParams,
|
||||
type UpdatePromptParams,
|
||||
} from './types';
|
||||
|
||||
const runtime = new AgentBuilderExecutionRuntime();
|
||||
|
||||
class AgentBuilderExecutor extends BaseExecutor<typeof AgentBuilderApiName> {
|
||||
readonly identifier = AgentBuilderIdentifier;
|
||||
protected readonly apiEnum = AgentBuilderApiName;
|
||||
|
||||
// ==================== Read Operations ====================
|
||||
|
||||
getAvailableModels = async (params: GetAvailableModelsParams): Promise<BuiltinToolResult> => {
|
||||
const result = await runtime.getAvailableModels(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
searchMarketTools = async (params: SearchMarketToolsParams): Promise<BuiltinToolResult> => {
|
||||
const result = await runtime.searchMarketTools(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
// ==================== Write Operations ====================
|
||||
|
||||
updateConfig = async (
|
||||
params: UpdateAgentConfigParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const agentId = ctx.agentId;
|
||||
|
||||
if (!agentId) {
|
||||
return {
|
||||
content: 'No active agent found',
|
||||
error: { message: 'No active agent found', type: 'NoAgentContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await runtime.updateAgentConfig(agentId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
updatePrompt = async (
|
||||
params: UpdatePromptParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const agentId = ctx.agentId;
|
||||
|
||||
if (!agentId) {
|
||||
return {
|
||||
content: 'No active agent found',
|
||||
error: { message: 'No active agent found', type: 'NoAgentContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await runtime.updatePrompt(agentId, {
|
||||
streaming: true,
|
||||
...params,
|
||||
});
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
installPlugin = async (
|
||||
params: InstallPluginParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const agentId = ctx.agentId;
|
||||
|
||||
if (!agentId) {
|
||||
return {
|
||||
content: 'No active agent found',
|
||||
error: { message: 'No active agent found', type: 'NoAgentContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await runtime.installPlugin(agentId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const agentBuilderExecutor = new AgentBuilderExecutor();
|
||||
@@ -6,20 +6,11 @@ import { Check, X } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type ExecuteCodeState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
@@ -42,13 +33,13 @@ export const ExecuteCodeInspector = memo<
|
||||
if (isArgumentsStreaming) {
|
||||
if (!description)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-cloud-sandbox.apiName.executeCode')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-cloud-sandbox.apiName.executeCode')}: </span>
|
||||
<span className={highlightTextStyles.gold}>{description}</span>
|
||||
</div>
|
||||
@@ -56,7 +47,7 @@ export const ExecuteCodeInspector = memo<
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span style={{ marginInlineStart: 2 }}>
|
||||
<span>{t('builtins.lobe-cloud-sandbox.apiName.executeCode')}: </span>
|
||||
{description && <span className={highlightTextStyles.primary}>{description}</span>}
|
||||
|
||||
@@ -6,19 +6,11 @@ import { Check, X } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type RunCommandState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
@@ -41,13 +33,13 @@ export const RunCommandInspector = memo<BuiltinInspectorProps<RunCommandParams,
|
||||
if (isArgumentsStreaming) {
|
||||
if (!description)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-cloud-sandbox.apiName.runCommand')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-cloud-sandbox.apiName.runCommand')}: </span>
|
||||
<span className={highlightTextStyles.primary}>{description}</span>
|
||||
</div>
|
||||
@@ -55,7 +47,7 @@ export const RunCommandInspector = memo<BuiltinInspectorProps<RunCommandParams,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span style={{ marginInlineStart: 2 }}>
|
||||
<span>{t('builtins.lobe-cloud-sandbox.apiName.runCommand')}: </span>
|
||||
{description && <span className={highlightTextStyles.primary}>{description}</span>}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./client": "./src/client/index.ts",
|
||||
"./executor": "./src/executor.ts",
|
||||
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
@@ -14,6 +16,10 @@
|
||||
"@lobechat/types": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
"@lobehub/ui": "^4",
|
||||
"antd": "^6",
|
||||
"lucide-react": "*",
|
||||
"react": "*",
|
||||
"react-i18next": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import type { BuiltinServerRuntimeOutput } from '@lobechat/types';
|
||||
|
||||
import { chatGroupService } from '@/services/chatGroup';
|
||||
import { agentService } from '@/services/agent';
|
||||
import { type GroupMemberConfig, chatGroupService } from '@/services/chatGroup';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { getChatGroupStoreState } from '@/store/agentGroup';
|
||||
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
||||
|
||||
import type {
|
||||
BatchCreateAgentsParams,
|
||||
BatchCreateAgentsState,
|
||||
CreateAgentParams,
|
||||
CreateAgentState,
|
||||
InviteAgentParams,
|
||||
InviteAgentState,
|
||||
RemoveAgentParams,
|
||||
RemoveAgentState,
|
||||
UpdateGroupConfigParams,
|
||||
UpdateGroupConfigState,
|
||||
SearchAgentParams,
|
||||
SearchAgentState,
|
||||
UpdateAgentPromptParams,
|
||||
UpdateGroupParams,
|
||||
UpdateGroupPromptParams,
|
||||
UpdateGroupPromptState,
|
||||
UpdateGroupState,
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
@@ -21,7 +30,184 @@ import type {
|
||||
* Extends AgentBuilder functionality with group-specific operations
|
||||
*/
|
||||
export class GroupAgentBuilderExecutionRuntime {
|
||||
// ==================== Group-specific Operations ====================
|
||||
// ==================== Group Member Management ====================
|
||||
|
||||
/**
|
||||
* Search for agents that can be invited to the group
|
||||
*/
|
||||
async searchAgent(args: SearchAgentParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
const { query, limit = 10 } = args;
|
||||
|
||||
try {
|
||||
const results = await agentService.queryAgents({ keyword: query, limit });
|
||||
|
||||
const agents = results.map((agent) => ({
|
||||
avatar: agent.avatar,
|
||||
description: agent.description,
|
||||
id: agent.id,
|
||||
title: agent.title,
|
||||
}));
|
||||
|
||||
const total = agents.length;
|
||||
|
||||
if (total === 0) {
|
||||
return {
|
||||
content: query
|
||||
? `No agents found matching "${query}".`
|
||||
: 'No agents found. You can create a new agent or search with different keywords.',
|
||||
state: { agents: [], query, total: 0 } as SearchAgentState,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Format agents list for LLM consumption
|
||||
const agentList = agents
|
||||
.map(
|
||||
(a, i) =>
|
||||
`${i + 1}. ${a.title || 'Untitled'} (ID: ${a.id})${a.description ? ` - ${a.description}` : ''}`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
return {
|
||||
content: query
|
||||
? `Found ${total} agent${total > 1 ? 's' : ''} matching "${query}":\n${agentList}`
|
||||
: `Found ${total} agent${total > 1 ? 's' : ''}:\n${agentList}`,
|
||||
state: { agents, query, total } as SearchAgentState,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to search agents: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new agent and add it to the group
|
||||
*/
|
||||
async createAgent(groupId: string, args: CreateAgentParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.getGroupById(groupId)(state);
|
||||
|
||||
if (!group) {
|
||||
return {
|
||||
content: 'Group not found',
|
||||
error: 'Group not found',
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Create a virtual agent only (no session needed for group agents)
|
||||
// Map 'tools' from LLM input to 'plugins' for internal API
|
||||
const result = await agentService.createAgentOnly({
|
||||
config: {
|
||||
avatar: args.avatar,
|
||||
description: args.description,
|
||||
plugins: args.tools,
|
||||
systemRole: args.systemRole,
|
||||
title: args.title,
|
||||
virtual: true,
|
||||
},
|
||||
groupId,
|
||||
});
|
||||
|
||||
if (!result.agentId) {
|
||||
return {
|
||||
content: 'Failed to create agent: No agent ID returned',
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Refresh the group detail in the store
|
||||
await state.refreshGroupDetail(groupId);
|
||||
|
||||
return {
|
||||
content: `Successfully created agent "${args.title}" and added it to the group.`,
|
||||
state: {
|
||||
agentId: result.agentId,
|
||||
success: true,
|
||||
title: args.title,
|
||||
} as CreateAgentState,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to create agent: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multiple agents at once and add them to the group
|
||||
* Uses batch API for efficiency (single request instead of N requests)
|
||||
*/
|
||||
async batchCreateAgents(
|
||||
groupId: string,
|
||||
args: BatchCreateAgentsParams,
|
||||
): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.getGroupById(groupId)(state);
|
||||
|
||||
if (!group) {
|
||||
return {
|
||||
content: 'Group not found',
|
||||
error: 'Group not found',
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Use batch API to create all agents in one request
|
||||
// Map 'tools' from LLM input to 'plugins' for internal API
|
||||
const agentConfigs: GroupMemberConfig[] = args.agents.map((agentDef) => ({
|
||||
avatar: agentDef.avatar,
|
||||
description: agentDef.description,
|
||||
plugins: agentDef.tools,
|
||||
systemRole: agentDef.systemRole,
|
||||
title: agentDef.title,
|
||||
}));
|
||||
|
||||
const { agents: createdAgents } = await chatGroupService.batchCreateAgentsInGroup(
|
||||
groupId,
|
||||
agentConfigs,
|
||||
);
|
||||
|
||||
// Refresh the group detail in the store
|
||||
await state.refreshGroupDetail(groupId);
|
||||
|
||||
const results = createdAgents.map((agent, index) => ({
|
||||
agentId: agent.id,
|
||||
success: true,
|
||||
title: args.agents[index].title,
|
||||
}));
|
||||
|
||||
const createdList = results.map((r) => `- ${r.title} (ID: ${r.agentId})`).join('\n');
|
||||
|
||||
return {
|
||||
content: `Successfully created ${results.length} agent${results.length > 1 ? 's' : ''}:\n${createdList}`,
|
||||
state: {
|
||||
agents: results,
|
||||
failedCount: 0,
|
||||
successCount: results.length,
|
||||
} as BatchCreateAgentsState,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to create agents: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite an agent to the group
|
||||
@@ -149,11 +335,58 @@ export class GroupAgentBuilderExecutionRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Group Configuration ====================
|
||||
|
||||
/**
|
||||
* Update group system prompt
|
||||
* Unlike regular AgentBuilder, this updates the group's systemPrompt in groupConfig
|
||||
* Update a specific agent's system prompt (systemRole)
|
||||
*/
|
||||
async updatePrompt(args: UpdateGroupPromptParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
async updateAgentPrompt(
|
||||
groupId: string,
|
||||
args: UpdateAgentPromptParams,
|
||||
): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const { agentId, prompt } = args;
|
||||
|
||||
// Get previous prompt for state
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.getGroupById(groupId)(state);
|
||||
const agent = group?.agents?.find((a) => a.id === agentId);
|
||||
const previousPrompt = agent?.systemRole ?? undefined;
|
||||
|
||||
// Update the agent's systemRole via agent store
|
||||
await useAgentStore.getState().updateAgentConfigById(agentId, { systemRole: prompt });
|
||||
|
||||
// Refresh the group detail in the store to sync agent data
|
||||
await state.refreshGroupDetail(groupId);
|
||||
|
||||
const content = prompt
|
||||
? `Successfully updated agent ${agentId} system prompt (${prompt.length} characters)`
|
||||
: `Successfully cleared agent ${agentId} system prompt`;
|
||||
|
||||
return {
|
||||
content,
|
||||
state: {
|
||||
agentId,
|
||||
newPrompt: prompt,
|
||||
previousPrompt,
|
||||
success: true,
|
||||
},
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to update agent prompt: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update group configuration and metadata (unified method)
|
||||
*/
|
||||
async updateGroup(args: UpdateGroupParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.currentGroup(state);
|
||||
@@ -166,19 +399,122 @@ export class GroupAgentBuilderExecutionRuntime {
|
||||
};
|
||||
}
|
||||
|
||||
const previousPrompt = group.config?.systemPrompt;
|
||||
const { config, meta } = args;
|
||||
|
||||
if (!config && !meta) {
|
||||
return {
|
||||
content: 'No configuration or metadata provided',
|
||||
error: 'No configuration or metadata provided',
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const updatedFields: string[] = [];
|
||||
const resultState: UpdateGroupState = { success: true };
|
||||
|
||||
// Update config if provided
|
||||
if (config) {
|
||||
const configUpdate: { openingMessage?: string; openingQuestions?: string[] } = {};
|
||||
|
||||
if (config.openingMessage !== undefined) {
|
||||
configUpdate.openingMessage = config.openingMessage;
|
||||
updatedFields.push(
|
||||
config.openingMessage
|
||||
? `openingMessage (${config.openingMessage.length} chars)`
|
||||
: 'openingMessage (cleared)',
|
||||
);
|
||||
}
|
||||
|
||||
if (config.openingQuestions !== undefined) {
|
||||
configUpdate.openingQuestions = config.openingQuestions;
|
||||
updatedFields.push(
|
||||
config.openingQuestions.length > 0
|
||||
? `openingQuestions (${config.openingQuestions.length} questions)`
|
||||
: 'openingQuestions (cleared)',
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(configUpdate).length > 0) {
|
||||
await state.updateGroupConfig(configUpdate);
|
||||
resultState.updatedConfig = configUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
// Update meta if provided
|
||||
if (meta && Object.keys(meta).length > 0) {
|
||||
await state.updateGroupMeta(meta);
|
||||
resultState.updatedMeta = meta;
|
||||
|
||||
if (meta.avatar !== undefined) {
|
||||
updatedFields.push(`avatar (${meta.avatar || 'cleared'})`);
|
||||
}
|
||||
if (meta.title !== undefined) {
|
||||
updatedFields.push(`title (${meta.title || 'cleared'})`);
|
||||
}
|
||||
if (meta.description !== undefined) {
|
||||
updatedFields.push(
|
||||
meta.description
|
||||
? `description (${meta.description.length} chars)`
|
||||
: 'description (cleared)',
|
||||
);
|
||||
}
|
||||
if (meta.backgroundColor !== undefined) {
|
||||
updatedFields.push(`backgroundColor (${meta.backgroundColor || 'cleared'})`);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the group detail in the store to ensure data sync
|
||||
await state.refreshGroupDetail(group.id);
|
||||
|
||||
const content = `Successfully updated group: ${updatedFields.join(', ')}`;
|
||||
|
||||
return {
|
||||
content,
|
||||
state: resultState,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to update group: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update group shared prompt/content
|
||||
*/
|
||||
async updateGroupPrompt(args: UpdateGroupPromptParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.currentGroup(state);
|
||||
|
||||
if (!group) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: 'No active group found',
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const previousPrompt = group.content ?? undefined;
|
||||
|
||||
if (args.streaming) {
|
||||
// Use streaming mode for typewriter effect
|
||||
await this.streamUpdatePrompt(args.prompt);
|
||||
await this.streamUpdateGroupPrompt(args.prompt);
|
||||
} else {
|
||||
// Update the system prompt directly
|
||||
await state.updateGroupConfig({ systemPrompt: args.prompt });
|
||||
// Update the content directly
|
||||
await state.updateGroup(group.id, { content: args.prompt });
|
||||
}
|
||||
|
||||
// Refresh the group detail in the store to ensure data sync
|
||||
await state.refreshGroupDetail(group.id);
|
||||
|
||||
const content = args.prompt
|
||||
? `Successfully updated group system prompt (${args.prompt.length} characters)`
|
||||
: 'Successfully cleared group system prompt';
|
||||
? `Successfully updated group shared prompt (${args.prompt.length} characters)`
|
||||
: 'Successfully cleared group shared prompt';
|
||||
|
||||
return {
|
||||
content,
|
||||
@@ -204,106 +540,14 @@ export class GroupAgentBuilderExecutionRuntime {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update group configuration (openingMessage, openingQuestions)
|
||||
* Stream update group prompt with typewriter effect
|
||||
*/
|
||||
async updateGroupConfig(args: UpdateGroupConfigParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.currentGroup(state);
|
||||
|
||||
if (!group) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: 'No active group found',
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const { config } = args;
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
content: 'No configuration provided',
|
||||
error: 'No configuration provided',
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Build the config update object
|
||||
const configUpdate: { openingMessage?: string; openingQuestions?: string[] } = {};
|
||||
|
||||
if (config.openingMessage !== undefined) {
|
||||
configUpdate.openingMessage = config.openingMessage;
|
||||
}
|
||||
|
||||
if (config.openingQuestions !== undefined) {
|
||||
configUpdate.openingQuestions = config.openingQuestions;
|
||||
}
|
||||
|
||||
// Update the group config
|
||||
await state.updateGroupConfig(configUpdate);
|
||||
|
||||
const updatedFields: string[] = [];
|
||||
if (config.openingMessage !== undefined) {
|
||||
updatedFields.push(
|
||||
config.openingMessage
|
||||
? `openingMessage (${config.openingMessage.length} chars)`
|
||||
: 'openingMessage (cleared)',
|
||||
);
|
||||
}
|
||||
if (config.openingQuestions !== undefined) {
|
||||
updatedFields.push(
|
||||
config.openingQuestions.length > 0
|
||||
? `openingQuestions (${config.openingQuestions.length} questions)`
|
||||
: 'openingQuestions (cleared)',
|
||||
);
|
||||
}
|
||||
|
||||
const content = `Successfully updated group configuration: ${updatedFields.join(', ')}`;
|
||||
|
||||
return {
|
||||
content,
|
||||
state: {
|
||||
success: true,
|
||||
updatedConfig: configUpdate,
|
||||
} as UpdateGroupConfigState,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
return {
|
||||
content: `Failed to update group configuration: ${err.message}`,
|
||||
error,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream update prompt with typewriter effect for group
|
||||
*/
|
||||
private async streamUpdatePrompt(prompt: string): Promise<void> {
|
||||
private async streamUpdateGroupPrompt(prompt: string): Promise<void> {
|
||||
const state = getChatGroupStoreState();
|
||||
const group = agentGroupSelectors.currentGroup(state);
|
||||
|
||||
// Start streaming
|
||||
state.startStreamingSystemPrompt();
|
||||
if (!group) return;
|
||||
|
||||
// Simulate streaming by chunking the content
|
||||
const chunkSize = 5; // Characters per chunk
|
||||
const delay = 10; // Milliseconds between chunks
|
||||
|
||||
for (let i = 0; i < prompt.length; i += chunkSize) {
|
||||
const chunk = prompt.slice(i, i + chunkSize);
|
||||
getChatGroupStoreState().appendStreamingSystemPrompt(chunk);
|
||||
|
||||
// Small delay for typewriter effect
|
||||
if (i + chunkSize < prompt.length) {
|
||||
// eslint-disable-next-line no-promise-executor-return
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
|
||||
// Finish streaming - EditorCanvas will handle save when streaming ends
|
||||
await getChatGroupStoreState().finishStreamingSystemPrompt();
|
||||
await state.updateGroup(group.id, { content: prompt });
|
||||
}
|
||||
}
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { Check } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { BatchCreateAgentsParams, BatchCreateAgentsState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar: cv }) => ({
|
||||
avatarGroup: css`
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
`,
|
||||
count: css`
|
||||
color: ${cv.colorTextSecondary};
|
||||
font-size: 12px;
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`,
|
||||
statusIcon: css`
|
||||
flex-shrink: 0;
|
||||
margin-block-end: -2px;
|
||||
`,
|
||||
title: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cv.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const BatchCreateAgentsInspector = memo<
|
||||
BuiltinInspectorProps<BatchCreateAgentsParams, BatchCreateAgentsState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const agents = args?.agents || partialArgs?.agents;
|
||||
|
||||
// Get display info from agents
|
||||
const displayInfo = useMemo(() => {
|
||||
if (!agents || agents.length === 0) return null;
|
||||
|
||||
const count = agents.length;
|
||||
const displayAgents = agents.slice(0, 3); // Show up to 3 avatars
|
||||
|
||||
return { count, displayAgents };
|
||||
}, [agents]);
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !displayInfo) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.batchCreateAgents')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isSuccess = pluginState?.successCount === pluginState?.agents?.length;
|
||||
const successCount = pluginState?.successCount ?? 0;
|
||||
const totalCount = displayInfo?.count ?? 0;
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(styles.root, (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText)}
|
||||
gap={8}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.title}>
|
||||
{t('builtins.lobe-group-agent-builder.apiName.batchCreateAgents')}:
|
||||
</span>
|
||||
{displayInfo && (
|
||||
<>
|
||||
<div className={styles.avatarGroup}>
|
||||
{displayInfo.displayAgents.map((agent, index) => (
|
||||
<Avatar
|
||||
key={index}
|
||||
avatar={agent.avatar}
|
||||
shape={'square'}
|
||||
size={20}
|
||||
title={agent.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<span className={styles.count}>
|
||||
{pluginState
|
||||
? `${successCount}/${totalCount}`
|
||||
: `${totalCount} ${t('builtins.lobe-group-agent-builder.inspector.agents')}`}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{!isLoading && isSuccess && (
|
||||
<Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
BatchCreateAgentsInspector.displayName = 'BatchCreateAgentsInspector';
|
||||
|
||||
export default BatchCreateAgentsInspector;
|
||||
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { Check } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { CreateAgentParams, CreateAgentState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar: cv }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`,
|
||||
statusIcon: css`
|
||||
flex-shrink: 0;
|
||||
margin-block-end: -2px;
|
||||
`,
|
||||
title: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cv.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const CreateAgentInspector = memo<
|
||||
BuiltinInspectorProps<CreateAgentParams, CreateAgentState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const title = args?.title || partialArgs?.title;
|
||||
const avatar = args?.avatar || partialArgs?.avatar;
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !title) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.createAgent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isSuccess = pluginState?.success;
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(styles.root, (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText)}
|
||||
gap={8}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.title}>
|
||||
{t('builtins.lobe-group-agent-builder.apiName.createAgent')}:
|
||||
</span>
|
||||
{avatar && <Avatar avatar={avatar} shape={'square'} size={20} title={title || undefined} />}
|
||||
{title && <span>{title}</span>}
|
||||
{!isLoading && isSuccess && (
|
||||
<Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
CreateAgentInspector.displayName = 'CreateAgentInspector';
|
||||
|
||||
export default CreateAgentInspector;
|
||||
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { Check } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { InviteAgentParams, InviteAgentState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const InviteAgentInspector = memo<
|
||||
BuiltinInspectorProps<InviteAgentParams, InviteAgentState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const agentId = args?.agentId || partialArgs?.agentId;
|
||||
const displayName = pluginState?.agentName || agentId;
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !agentId) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.inviteAgent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isSuccess = pluginState?.success;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.inviteAgent')}: </span>
|
||||
{displayName && <span className={highlightTextStyles.primary}>{displayName}</span>}
|
||||
{!isLoading && isSuccess && (
|
||||
<Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
InviteAgentInspector.displayName = 'InviteAgentInspector';
|
||||
|
||||
export default InviteAgentInspector;
|
||||
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { Check } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { RemoveAgentParams, RemoveAgentState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const RemoveAgentInspector = memo<
|
||||
BuiltinInspectorProps<RemoveAgentParams, RemoveAgentState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const agentId = args?.agentId || partialArgs?.agentId;
|
||||
const displayName = pluginState?.agentName || agentId;
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !agentId) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.removeAgent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isSuccess = pluginState?.success;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.removeAgent')}: </span>
|
||||
{displayName && <span className={highlightTextStyles.primary}>{displayName}</span>}
|
||||
{!isLoading && isSuccess && (
|
||||
<Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
RemoveAgentInspector.displayName = 'RemoveAgentInspector';
|
||||
|
||||
export default RemoveAgentInspector;
|
||||
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import { cssVar, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { SearchAgentParams, SearchAgentState } from '../../../types';
|
||||
|
||||
export const SearchAgentInspector = memo<
|
||||
BuiltinInspectorProps<SearchAgentParams, SearchAgentState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const query = args?.query || partialArgs?.query;
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !query) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.searchAgent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const resultCount = pluginState?.total ?? pluginState?.agents?.length ?? 0;
|
||||
const hasResults = resultCount > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.searchAgent')}</span>
|
||||
{query && (
|
||||
<>
|
||||
: <span className={highlightTextStyles.primary}>{query}</span>
|
||||
</>
|
||||
)}
|
||||
{!isLoading &&
|
||||
!isArgumentsStreaming &&
|
||||
pluginState?.agents &&
|
||||
(hasResults ? (
|
||||
<span style={{ marginInlineStart: 4 }}>({resultCount})</span>
|
||||
) : (
|
||||
<Text
|
||||
as={'span'}
|
||||
color={cssVar.colorTextDescription}
|
||||
fontSize={12}
|
||||
style={{ marginInlineStart: 4 }}
|
||||
>
|
||||
({t('builtins.lobe-group-agent-builder.inspector.noResults')})
|
||||
</Text>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SearchAgentInspector.displayName = 'SearchAgentInspector';
|
||||
|
||||
export default SearchAgentInspector;
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox, Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
||||
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { UpdateAgentPromptParams, UpdateAgentPromptState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar: cv }) => ({
|
||||
agentName: css`
|
||||
overflow: hidden;
|
||||
|
||||
max-width: 120px;
|
||||
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
label: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cv.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdateAgentPromptInspector = memo<
|
||||
BuiltinInspectorProps<UpdateAgentPromptParams, UpdateAgentPromptState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const agentId = args?.agentId || partialArgs?.agentId;
|
||||
const prompt = args?.prompt || partialArgs?.prompt;
|
||||
|
||||
// Get agent info from the current group
|
||||
const agent = useAgentGroupStore((s) => {
|
||||
const agents = agentGroupSelectors.currentGroupAgents(s);
|
||||
return agents.find((a) => a.id === agentId);
|
||||
});
|
||||
|
||||
// Calculate length difference
|
||||
const lengthDiff = useMemo(() => {
|
||||
if (!pluginState) return null;
|
||||
|
||||
const newLength = pluginState.newPrompt?.length ?? 0;
|
||||
const prevLength = pluginState.previousPrompt?.length ?? 0;
|
||||
return newLength - prevLength;
|
||||
}, [pluginState]);
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !agentId) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.updateAgentPrompt')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const streamingLength = prompt?.length ?? 0;
|
||||
|
||||
const isSupervisor = agent?.isSupervisor ?? false;
|
||||
|
||||
// Use different i18n key for supervisor
|
||||
const labelKey = isSupervisor
|
||||
? 'builtins.lobe-group-agent-builder.apiName.updateSupervisorPrompt'
|
||||
: 'builtins.lobe-group-agent-builder.apiName.updateAgentPrompt';
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align="center"
|
||||
className={cx(styles.root, (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText)}
|
||||
gap={6}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.label}>{t(labelKey)}</span>
|
||||
{/* Only show avatar and title for non-supervisor agents */}
|
||||
{agent && !isSupervisor && (
|
||||
<>
|
||||
<Avatar avatar={agent.avatar ?? undefined} size={18} title={agent.title ?? undefined} />
|
||||
<span className={styles.agentName}>{agent.title}</span>
|
||||
</>
|
||||
)}
|
||||
{/* Show length diff when completed */}
|
||||
{!isLoading && !isArgumentsStreaming && lengthDiff !== null && (
|
||||
<Text
|
||||
as="span"
|
||||
code
|
||||
color={lengthDiff >= 0 ? cssVar.colorSuccess : cssVar.colorError}
|
||||
fontSize={12}
|
||||
>
|
||||
{lengthDiff >= 0 ? '+' : ''}
|
||||
{lengthDiff}
|
||||
{t('builtins.lobe-agent-builder.inspector.chars')}
|
||||
</Text>
|
||||
)}
|
||||
{/* Show streaming length */}
|
||||
{(isArgumentsStreaming || isLoading) && streamingLength > 0 && (
|
||||
<Text as="span" code color={cssVar.colorTextDescription} fontSize={12}>
|
||||
({streamingLength}
|
||||
{t('builtins.lobe-agent-builder.inspector.chars')})
|
||||
</Text>
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
UpdateAgentPromptInspector.displayName = 'UpdateAgentPromptInspector';
|
||||
|
||||
export default UpdateAgentPromptInspector;
|
||||
@@ -0,0 +1,87 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { Check } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { UpdateGroupParams, UpdateGroupState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdateGroupInspector = memo<BuiltinInspectorProps<UpdateGroupParams, UpdateGroupState>>(
|
||||
({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const config = args?.config || partialArgs?.config;
|
||||
const meta = args?.meta || partialArgs?.meta;
|
||||
|
||||
// Build display text from updated fields
|
||||
const displayText = useMemo(() => {
|
||||
const fields: string[] = [];
|
||||
// Config fields
|
||||
if (config?.openingMessage !== undefined) {
|
||||
fields.push(t('builtins.lobe-group-agent-builder.inspector.openingMessage'));
|
||||
}
|
||||
if (config?.openingQuestions !== undefined) {
|
||||
fields.push(t('builtins.lobe-group-agent-builder.inspector.openingQuestions'));
|
||||
}
|
||||
// Meta fields
|
||||
if (meta?.title !== undefined) {
|
||||
fields.push(t('builtins.lobe-group-agent-builder.inspector.title'));
|
||||
}
|
||||
if (meta?.description !== undefined) {
|
||||
fields.push(t('builtins.lobe-group-agent-builder.inspector.description'));
|
||||
}
|
||||
if (meta?.avatar !== undefined) {
|
||||
fields.push(t('builtins.lobe-group-agent-builder.inspector.avatar'));
|
||||
}
|
||||
if (meta?.backgroundColor !== undefined) {
|
||||
fields.push(t('builtins.lobe-group-agent-builder.inspector.backgroundColor'));
|
||||
}
|
||||
return fields.length > 0 ? fields.join(', ') : '';
|
||||
}, [config, meta, t]);
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !displayText) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.updateGroup')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isSuccess = pluginState?.success;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.updateGroup')}</span>
|
||||
{displayText && (
|
||||
<>
|
||||
: <span className={highlightTextStyles.primary}>{displayText}</span>
|
||||
</>
|
||||
)}
|
||||
{!isLoading && isSuccess && (
|
||||
<Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
UpdateGroupInspector.displayName = 'UpdateGroupInspector';
|
||||
|
||||
export default UpdateGroupInspector;
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Flexbox, Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { UpdateGroupPromptParams, UpdateGroupPromptState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar: cv }) => ({
|
||||
groupName: css`
|
||||
overflow: hidden;
|
||||
|
||||
max-width: 120px;
|
||||
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
label: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cv.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdateGroupPromptInspector = memo<
|
||||
BuiltinInspectorProps<UpdateGroupPromptParams, UpdateGroupPromptState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const prompt = args?.prompt || partialArgs?.prompt;
|
||||
|
||||
// Calculate length difference
|
||||
const lengthDiff = useMemo(() => {
|
||||
if (!pluginState) return null;
|
||||
|
||||
const newLength = pluginState.newPrompt?.length ?? 0;
|
||||
const prevLength = pluginState.previousPrompt?.length ?? 0;
|
||||
return newLength - prevLength;
|
||||
}, [pluginState]);
|
||||
|
||||
// Initial streaming state
|
||||
if (isArgumentsStreaming && !prompt) {
|
||||
return (
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-agent-builder.apiName.updateGroupPrompt')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const streamingLength = prompt?.length ?? 0;
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align="center"
|
||||
className={cx(styles.root, (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText)}
|
||||
gap={6}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.label}>
|
||||
{t('builtins.lobe-group-agent-builder.apiName.updateGroupPrompt')}
|
||||
</span>
|
||||
{/* Show length diff when completed */}
|
||||
{!isLoading && !isArgumentsStreaming && lengthDiff !== null && (
|
||||
<Text
|
||||
as="span"
|
||||
code
|
||||
color={lengthDiff >= 0 ? cssVar.colorSuccess : cssVar.colorError}
|
||||
fontSize={12}
|
||||
>
|
||||
{lengthDiff >= 0 ? '+' : ''}
|
||||
{lengthDiff}
|
||||
{t('builtins.lobe-agent-builder.inspector.chars')}
|
||||
</Text>
|
||||
)}
|
||||
{/* Show streaming length */}
|
||||
{(isArgumentsStreaming || isLoading) && streamingLength > 0 && (
|
||||
<Text as="span" code color={cssVar.colorTextDescription} fontSize={12}>
|
||||
({streamingLength}
|
||||
{t('builtins.lobe-agent-builder.inspector.chars')})
|
||||
</Text>
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
UpdateGroupPromptInspector.displayName = 'UpdateGroupPromptInspector';
|
||||
|
||||
export default UpdateGroupPromptInspector;
|
||||
@@ -0,0 +1,52 @@
|
||||
// Import shared inspectors from agent-builder
|
||||
import {
|
||||
GetAvailableModelsInspector,
|
||||
InstallPluginInspector,
|
||||
SearchMarketToolsInspector,
|
||||
UpdateConfigInspector,
|
||||
} from '@lobechat/builtin-tool-agent-builder/client';
|
||||
import { type BuiltinInspector } from '@lobechat/types';
|
||||
|
||||
import { GroupAgentBuilderApiName } from '../../types';
|
||||
import { BatchCreateAgentsInspector } from './BatchCreateAgents';
|
||||
import { CreateAgentInspector } from './CreateAgent';
|
||||
import { InviteAgentInspector } from './InviteAgent';
|
||||
import { RemoveAgentInspector } from './RemoveAgent';
|
||||
import { SearchAgentInspector } from './SearchAgent';
|
||||
import { UpdateAgentPromptInspector } from './UpdateAgentPrompt';
|
||||
import { UpdateGroupInspector } from './UpdateGroup';
|
||||
import { UpdateGroupPromptInspector } from './UpdateGroupPrompt';
|
||||
|
||||
/**
|
||||
* Group Agent Builder Inspector Components Registry
|
||||
*
|
||||
* Inspector components customize the title/header area
|
||||
* of tool calls in the conversation UI.
|
||||
*/
|
||||
export const GroupAgentBuilderInspectors: Record<string, BuiltinInspector> = {
|
||||
// Group-specific inspectors
|
||||
[GroupAgentBuilderApiName.batchCreateAgents]: BatchCreateAgentsInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.createAgent]: CreateAgentInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.inviteAgent]: InviteAgentInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.removeAgent]: RemoveAgentInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.searchAgent]: SearchAgentInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.updateAgentPrompt]: UpdateAgentPromptInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.updateGroup]: UpdateGroupInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.updateGroupPrompt]: UpdateGroupPromptInspector as BuiltinInspector,
|
||||
|
||||
// Shared inspectors from agent-builder (reused for group context)
|
||||
[GroupAgentBuilderApiName.getAvailableModels]: GetAvailableModelsInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.installPlugin]: InstallPluginInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.searchMarketTools]: SearchMarketToolsInspector as BuiltinInspector,
|
||||
[GroupAgentBuilderApiName.updateAgentConfig]: UpdateConfigInspector as BuiltinInspector,
|
||||
};
|
||||
|
||||
// Re-export individual inspectors
|
||||
export { BatchCreateAgentsInspector } from './BatchCreateAgents';
|
||||
export { CreateAgentInspector } from './CreateAgent';
|
||||
export { InviteAgentInspector } from './InviteAgent';
|
||||
export { RemoveAgentInspector } from './RemoveAgent';
|
||||
export { SearchAgentInspector } from './SearchAgent';
|
||||
export { UpdateAgentPromptInspector } from './UpdateAgentPrompt';
|
||||
export { UpdateGroupInspector } from './UpdateGroup';
|
||||
export { UpdateGroupPromptInspector } from './UpdateGroupPrompt';
|
||||
@@ -0,0 +1,103 @@
|
||||
'use client';
|
||||
|
||||
import { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { Users } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { BatchCreateAgentsParams, BatchCreateAgentsState } from '../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 4px 16px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
border-radius: 8px;
|
||||
`,
|
||||
description: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: ${cssVar.colorTextDescription};
|
||||
text-overflow: ellipsis;
|
||||
`,
|
||||
empty: css`
|
||||
padding: 16px;
|
||||
color: ${cssVar.colorTextTertiary};
|
||||
`,
|
||||
item: css`
|
||||
padding-block: 12px;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-block-end: 1px solid ${cssVar.colorBorderSecondary};
|
||||
}
|
||||
`,
|
||||
title: css`
|
||||
overflow: hidden;
|
||||
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
interface AgentItemProps {
|
||||
agent: {
|
||||
agentId: string;
|
||||
success: boolean;
|
||||
title: string;
|
||||
};
|
||||
definition?: {
|
||||
avatar?: string;
|
||||
description?: string;
|
||||
title: string;
|
||||
};
|
||||
}
|
||||
|
||||
const AgentItem = memo<AgentItemProps>(({ agent, definition }) => {
|
||||
const avatar = definition?.avatar;
|
||||
const description = definition?.description;
|
||||
|
||||
return (
|
||||
<Flexbox align="center" className={styles.item} gap={12} horizontal>
|
||||
<Avatar avatar={avatar} size={24} style={{ flexShrink: 0 }} title={agent.title} />
|
||||
<Flexbox flex={1} gap={2} style={{ minWidth: 0, overflow: 'hidden' }}>
|
||||
<span className={styles.title}>{agent.title}</span>
|
||||
{description && <span className={styles.description}>{description}</span>}
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
const BatchCreateAgentsRender = memo<
|
||||
BuiltinRenderProps<BatchCreateAgentsParams, BatchCreateAgentsState>
|
||||
>(({ args, pluginState }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { agents: resultAgents } = pluginState || {};
|
||||
const definitions = args?.agents || [];
|
||||
|
||||
if (!resultAgents || resultAgents.length === 0) {
|
||||
return (
|
||||
<Flexbox align="center" className={styles.empty} gap={8}>
|
||||
<Users size={24} />
|
||||
<span>{t('builtins.lobe-group-agent-builder.inspector.noResults')}</span>
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox className={styles.container}>
|
||||
{resultAgents.map((agent, index) => (
|
||||
<AgentItem agent={agent} definition={definitions[index]} key={agent.agentId || index} />
|
||||
))}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
export default BatchCreateAgentsRender;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { UpdateAgentPromptParams, UpdateAgentPromptState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdateAgentPromptRender = memo<
|
||||
BuiltinRenderProps<UpdateAgentPromptParams, UpdateAgentPromptState>
|
||||
>(({ pluginState }) => {
|
||||
const prompt = pluginState?.newPrompt;
|
||||
|
||||
if (!prompt) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
<Markdown variant={'chat'}>{prompt}</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
UpdateAgentPromptRender.displayName = 'UpdateAgentPromptRender';
|
||||
|
||||
export default UpdateAgentPromptRender;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { UpdateGroupPromptParams, UpdateGroupPromptState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const UpdateGroupPromptRender = memo<
|
||||
BuiltinRenderProps<UpdateGroupPromptParams, UpdateGroupPromptState>
|
||||
>(({ pluginState }) => {
|
||||
const prompt = pluginState?.newPrompt;
|
||||
|
||||
if (!prompt) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
<Markdown variant={'chat'}>{prompt}</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
UpdateGroupPromptRender.displayName = 'UpdateGroupPromptRender';
|
||||
|
||||
export default UpdateGroupPromptRender;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { GroupAgentBuilderApiName } from '../../types';
|
||||
import BatchCreateAgents from './BatchCreateAgents';
|
||||
import UpdateAgentPrompt from './UpdateAgentPrompt';
|
||||
import UpdateGroupPrompt from './UpdateGroupPrompt';
|
||||
|
||||
/**
|
||||
* Group Agent Builder Render Components Registry
|
||||
*
|
||||
* Render components display the results of tool calls
|
||||
* in a user-friendly format.
|
||||
*/
|
||||
export const GroupAgentBuilderRenders = {
|
||||
[GroupAgentBuilderApiName.batchCreateAgents]: BatchCreateAgents,
|
||||
[GroupAgentBuilderApiName.updateAgentPrompt]: UpdateAgentPrompt,
|
||||
[GroupAgentBuilderApiName.updateGroupPrompt]: UpdateGroupPrompt,
|
||||
};
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinStreamingProps } from '@lobechat/types';
|
||||
import { Avatar, Block, Flexbox, Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { BatchCreateAgentsParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
description: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: ${cssVar.colorTextDescription};
|
||||
text-overflow: ellipsis;
|
||||
`,
|
||||
index: css`
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: ${cssVar.colorTextQuaternary};
|
||||
`,
|
||||
item: css`
|
||||
padding-block: 10px;
|
||||
padding-inline: 12px;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-block-end: 1px dashed ${cssVar.colorBorderSecondary};
|
||||
}
|
||||
`,
|
||||
systemRole: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: ${cssVar.colorTextTertiary};
|
||||
text-overflow: ellipsis;
|
||||
`,
|
||||
title: css`
|
||||
overflow: hidden;
|
||||
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const BatchCreateAgentsStreaming = memo<BuiltinStreamingProps<BatchCreateAgentsParams>>(
|
||||
({ args }) => {
|
||||
const { agents } = args || {};
|
||||
|
||||
if (!agents || agents.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Block variant={'outlined'} width="100%">
|
||||
{agents.map((agent, index) => (
|
||||
<Flexbox className={styles.item} gap={8} horizontal key={index}>
|
||||
<div className={styles.index}>{index + 1}.</div>
|
||||
<Avatar avatar={agent.avatar} size={24} style={{ flexShrink: 0 }} title={agent.title} />
|
||||
<Flexbox flex={1} gap={4} style={{ minWidth: 0, overflow: 'hidden' }}>
|
||||
<span className={styles.title}>{agent.title}</span>
|
||||
{agent.description && <span className={styles.description}>{agent.description}</span>}
|
||||
{agent.systemRole && (
|
||||
<div className={styles.systemRole}>
|
||||
<Markdown animated variant={'chat'}>
|
||||
{agent.systemRole}
|
||||
</Markdown>
|
||||
</div>
|
||||
)}
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
))}
|
||||
</Block>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BatchCreateAgentsStreaming.displayName = 'BatchCreateAgentsStreaming';
|
||||
|
||||
export default BatchCreateAgentsStreaming;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinStreamingProps } from '@lobechat/types';
|
||||
import { Block, Markdown } from '@lobehub/ui';
|
||||
import { memo, useEffect } from 'react';
|
||||
|
||||
import { useGroupProfileStore } from '@/store/groupProfile';
|
||||
|
||||
import type { UpdateAgentPromptParams } from '../../../types';
|
||||
|
||||
export const UpdateAgentPromptStreaming = memo<BuiltinStreamingProps<UpdateAgentPromptParams>>(
|
||||
({ args }) => {
|
||||
const { agentId, prompt } = args || {};
|
||||
const setActiveTabId = useGroupProfileStore((s) => s.setActiveTabId);
|
||||
|
||||
// Switch to agent tab when streaming agent prompt
|
||||
useEffect(() => {
|
||||
if (agentId) {
|
||||
setActiveTabId(agentId);
|
||||
}
|
||||
}, [agentId, setActiveTabId]);
|
||||
|
||||
if (!prompt) return null;
|
||||
|
||||
return (
|
||||
<Block padding={4} variant={'outlined'} width="100%">
|
||||
<Markdown animated variant={'chat'}>
|
||||
{prompt}
|
||||
</Markdown>
|
||||
</Block>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
UpdateAgentPromptStreaming.displayName = 'UpdateAgentPromptStreaming';
|
||||
|
||||
export default UpdateAgentPromptStreaming;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinStreamingProps } from '@lobechat/types';
|
||||
import { Block, Markdown } from '@lobehub/ui';
|
||||
import { memo, useEffect } from 'react';
|
||||
|
||||
import { useGroupProfileStore } from '@/store/groupProfile';
|
||||
|
||||
import type { UpdateGroupPromptParams } from '../../../types';
|
||||
|
||||
export const UpdateGroupPromptStreaming = memo<BuiltinStreamingProps<UpdateGroupPromptParams>>(
|
||||
({ args }) => {
|
||||
const { prompt } = args || {};
|
||||
const setActiveTabId = useGroupProfileStore((s) => s.setActiveTabId);
|
||||
|
||||
// Switch to group tab when streaming group prompt
|
||||
useEffect(() => {
|
||||
setActiveTabId('group');
|
||||
}, []);
|
||||
|
||||
if (!prompt) return null;
|
||||
|
||||
return (
|
||||
<Block padding={4} variant={'outlined'} width="100%">
|
||||
<Markdown animated variant={'chat'}>
|
||||
{prompt}
|
||||
</Markdown>
|
||||
</Block>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
UpdateGroupPromptStreaming.displayName = 'UpdateGroupPromptStreaming';
|
||||
|
||||
export default UpdateGroupPromptStreaming;
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { BuiltinStreaming } from '@lobechat/types';
|
||||
|
||||
import { GroupAgentBuilderApiName } from '../../types';
|
||||
import { BatchCreateAgentsStreaming } from './BatchCreateAgents';
|
||||
import { UpdateAgentPromptStreaming } from './UpdateAgentPrompt';
|
||||
import { UpdateGroupPromptStreaming } from './UpdateGroupPrompt';
|
||||
|
||||
/**
|
||||
* Group Agent Builder Streaming Components Registry
|
||||
*
|
||||
* Streaming components render tool calls while they are
|
||||
* still executing, allowing real-time feedback to users.
|
||||
*/
|
||||
export const GroupAgentBuilderStreamings: Record<string, BuiltinStreaming> = {
|
||||
[GroupAgentBuilderApiName.batchCreateAgents]: BatchCreateAgentsStreaming as BuiltinStreaming,
|
||||
[GroupAgentBuilderApiName.updateAgentPrompt]: UpdateAgentPromptStreaming as BuiltinStreaming,
|
||||
[GroupAgentBuilderApiName.updateGroupPrompt]: UpdateGroupPromptStreaming as BuiltinStreaming,
|
||||
};
|
||||
|
||||
export { BatchCreateAgentsStreaming } from './BatchCreateAgents';
|
||||
export { UpdateAgentPromptStreaming } from './UpdateAgentPrompt';
|
||||
export { UpdateGroupPromptStreaming } from './UpdateGroupPrompt';
|
||||
@@ -0,0 +1,26 @@
|
||||
// Inspector components (customized tool call headers)
|
||||
export { GroupAgentBuilderInspectors } from './Inspector';
|
||||
export {
|
||||
BatchCreateAgentsInspector,
|
||||
CreateAgentInspector,
|
||||
InviteAgentInspector,
|
||||
RemoveAgentInspector,
|
||||
SearchAgentInspector,
|
||||
UpdateAgentPromptInspector,
|
||||
UpdateGroupInspector,
|
||||
UpdateGroupPromptInspector,
|
||||
} from './Inspector';
|
||||
|
||||
// Render components (read-only result display)
|
||||
export { GroupAgentBuilderRenders } from './Render';
|
||||
|
||||
// Streaming components (real-time tool execution feedback)
|
||||
export {
|
||||
BatchCreateAgentsStreaming,
|
||||
GroupAgentBuilderStreamings,
|
||||
UpdateGroupPromptStreaming,
|
||||
} from './Streaming';
|
||||
|
||||
// Re-export types and manifest for convenience
|
||||
export { GroupAgentBuilderManifest } from '../manifest';
|
||||
export * from '../types';
|
||||
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Group Agent Builder Executor
|
||||
*
|
||||
* Handles all group agent builder tool calls for configuring groups and their agents.
|
||||
* Extends AgentBuilder functionality with group-specific operations.
|
||||
*/
|
||||
import type {
|
||||
GetAvailableModelsParams,
|
||||
InstallPluginParams,
|
||||
SearchMarketToolsParams,
|
||||
} from '@lobechat/builtin-tool-agent-builder';
|
||||
import { AgentBuilderExecutionRuntime } from '@lobechat/builtin-tool-agent-builder/executionRuntime';
|
||||
import { BaseExecutor, type BuiltinToolContext, type BuiltinToolResult } from '@lobechat/types';
|
||||
|
||||
import { GroupAgentBuilderExecutionRuntime } from './ExecutionRuntime';
|
||||
import {
|
||||
type BatchCreateAgentsParams,
|
||||
type CreateAgentParams,
|
||||
GroupAgentBuilderApiName,
|
||||
GroupAgentBuilderIdentifier,
|
||||
type InviteAgentParams,
|
||||
type RemoveAgentParams,
|
||||
type SearchAgentParams,
|
||||
type UpdateAgentConfigWithIdParams,
|
||||
type UpdateAgentPromptParams,
|
||||
type UpdateGroupParams,
|
||||
type UpdateGroupPromptParams,
|
||||
} from './types';
|
||||
|
||||
const agentBuilderRuntime = new AgentBuilderExecutionRuntime();
|
||||
const groupAgentBuilderRuntime = new GroupAgentBuilderExecutionRuntime();
|
||||
|
||||
class GroupAgentBuilderExecutor extends BaseExecutor<typeof GroupAgentBuilderApiName> {
|
||||
readonly identifier = GroupAgentBuilderIdentifier;
|
||||
protected readonly apiEnum = GroupAgentBuilderApiName;
|
||||
|
||||
// ==================== Group Member Management ====================
|
||||
|
||||
searchAgent = async (params: SearchAgentParams): Promise<BuiltinToolResult> => {
|
||||
const result = await groupAgentBuilderRuntime.searchAgent(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
createAgent = async (
|
||||
params: CreateAgentParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const groupId = ctx.groupId;
|
||||
|
||||
if (!groupId) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: { message: 'No active group found', type: 'NoGroupContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.createAgent(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
batchCreateAgents = async (
|
||||
params: BatchCreateAgentsParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const groupId = ctx.groupId;
|
||||
|
||||
if (!groupId) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: { message: 'No active group found', type: 'NoGroupContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.batchCreateAgents(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
inviteAgent = async (
|
||||
params: InviteAgentParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const groupId = ctx.groupId;
|
||||
|
||||
if (!groupId) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: { message: 'No active group found', type: 'NoGroupContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.inviteAgent(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
removeAgent = async (
|
||||
params: RemoveAgentParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const groupId = ctx.groupId;
|
||||
|
||||
if (!groupId) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: { message: 'No active group found', type: 'NoGroupContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.removeAgent(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
// ==================== Group Configuration ====================
|
||||
|
||||
updateAgentPrompt = async (
|
||||
params: UpdateAgentPromptParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const groupId = ctx.groupId;
|
||||
|
||||
if (!groupId) {
|
||||
return {
|
||||
content: 'No active group found',
|
||||
error: { message: 'No active group found', type: 'NoGroupContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await groupAgentBuilderRuntime.updateAgentPrompt(groupId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
updateGroup = async (params: UpdateGroupParams): Promise<BuiltinToolResult> => {
|
||||
const result = await groupAgentBuilderRuntime.updateGroup(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
updateGroupPrompt = async (params: UpdateGroupPromptParams): Promise<BuiltinToolResult> => {
|
||||
const result = await groupAgentBuilderRuntime.updateGroupPrompt({
|
||||
streaming: true,
|
||||
...params,
|
||||
});
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
// ==================== Inherited Operations (for supervisor agent) ====================
|
||||
|
||||
getAvailableModels = async (params: GetAvailableModelsParams): Promise<BuiltinToolResult> => {
|
||||
const result = await agentBuilderRuntime.getAvailableModels(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
searchMarketTools = async (params: SearchMarketToolsParams): Promise<BuiltinToolResult> => {
|
||||
const result = await agentBuilderRuntime.searchMarketTools(params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
updateConfig = async (
|
||||
params: UpdateAgentConfigWithIdParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
// Use provided agentId or fall back to supervisor agent from context
|
||||
const { agentId: paramAgentId, ...restParams } = params;
|
||||
const agentId = paramAgentId ?? ctx.agentId;
|
||||
|
||||
if (!agentId) {
|
||||
return {
|
||||
content: 'No agent found. Please provide an agentId or ensure supervisor context is available.',
|
||||
error: { message: 'No agent found', type: 'NoAgentContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await agentBuilderRuntime.updateAgentConfig(agentId, restParams);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
|
||||
installPlugin = async (
|
||||
params: InstallPluginParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const agentId = ctx.agentId;
|
||||
|
||||
if (!agentId) {
|
||||
return {
|
||||
content: 'No supervisor agent found',
|
||||
error: { message: 'No supervisor agent found', type: 'NoAgentContext' },
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await agentBuilderRuntime.installPlugin(agentId, params);
|
||||
return {
|
||||
content: result.content,
|
||||
error: result.error
|
||||
? { body: result.error, message: String(result.error), type: 'RuntimeError' }
|
||||
: undefined,
|
||||
state: result.state,
|
||||
success: result.success,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const groupAgentBuilderExecutor = new GroupAgentBuilderExecutor();
|
||||
@@ -1,16 +1,3 @@
|
||||
export { GroupAgentBuilderManifest } from './manifest';
|
||||
export { systemPrompt } from './systemRole';
|
||||
export {
|
||||
GroupAgentBuilderApiName,
|
||||
type GroupAgentBuilderApiNameType,
|
||||
GroupAgentBuilderIdentifier,
|
||||
type InviteAgentParams,
|
||||
type InviteAgentState,
|
||||
type RemoveAgentParams,
|
||||
type RemoveAgentState,
|
||||
type UpdateGroupConfigParams,
|
||||
type UpdateGroupConfigState,
|
||||
type UpdateGroupMetaParams,
|
||||
type UpdateGroupPromptParams,
|
||||
type UpdateGroupPromptState,
|
||||
} from './types';
|
||||
export * from './types';
|
||||
|
||||
@@ -6,6 +6,111 @@ import { GroupAgentBuilderApiName, GroupAgentBuilderIdentifier } from './types';
|
||||
export const GroupAgentBuilderManifest: BuiltinToolManifest = {
|
||||
api: [
|
||||
// ==================== Group Member Management ====================
|
||||
{
|
||||
description:
|
||||
"Search for agents that can be invited to the group. Returns agents from the user's collection. Use this to find suitable agents before inviting them.",
|
||||
name: GroupAgentBuilderApiName.searchAgent,
|
||||
parameters: {
|
||||
properties: {
|
||||
limit: {
|
||||
default: 10,
|
||||
description: 'Maximum number of results to return (default: 10, max: 20).',
|
||||
maximum: 20,
|
||||
minimum: 1,
|
||||
type: 'number',
|
||||
},
|
||||
query: {
|
||||
description:
|
||||
'Search query to find agents by name, description, or capabilities. Leave empty to browse all available agents.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Create a new agent dynamically based on user requirements and add it to the group. Use this when no existing agent matches the needed expertise.',
|
||||
humanIntervention: 'required',
|
||||
name: GroupAgentBuilderApiName.createAgent,
|
||||
parameters: {
|
||||
properties: {
|
||||
avatar: {
|
||||
description: "An emoji or image URL for the agent's avatar (optional).",
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
description: 'A brief description of what this agent does and its expertise.',
|
||||
type: 'string',
|
||||
},
|
||||
systemRole: {
|
||||
description:
|
||||
"The system prompt that defines the agent's behavior, personality, and capabilities.",
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
description: 'The display name for the new agent.',
|
||||
type: 'string',
|
||||
},
|
||||
tools: {
|
||||
description:
|
||||
'Array of tool identifiers to enable for this agent. Use identifiers from official_tools context (e.g., "lobe-cloud-sandbox", "web-crawler").',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
required: ['title', 'systemRole'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Create multiple agents at once and add them to the group. Use this to efficiently set up a team of agents with different expertise.',
|
||||
humanIntervention: 'required',
|
||||
name: GroupAgentBuilderApiName.batchCreateAgents,
|
||||
parameters: {
|
||||
properties: {
|
||||
agents: {
|
||||
description: 'Array of agent definitions to create',
|
||||
items: {
|
||||
properties: {
|
||||
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
||||
avatar: {
|
||||
description: "An emoji or image URL for the agent's avatar (optional).",
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
description: 'The display name for the new agent.',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
description: 'A brief description of what this agent does and its expertise.',
|
||||
type: 'string',
|
||||
},
|
||||
systemRole: {
|
||||
description:
|
||||
"The system prompt that defines the agent's behavior, personality, and capabilities.",
|
||||
type: 'string',
|
||||
},
|
||||
tools: {
|
||||
description:
|
||||
'Array of tool identifiers to enable for this agent. Use identifiers from official_tools context (e.g., "lobe-cloud-sandbox", "web-crawler").',
|
||||
items: { type: 'string' },
|
||||
type: 'array',
|
||||
},
|
||||
/* eslint-enable sort-keys-fix/sort-keys-fix */
|
||||
},
|
||||
required: ['avatar', 'title', 'description', 'systemRole'],
|
||||
type: 'object',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
required: ['agents'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Invite an existing agent to join the group. The agent will become a member and participate in group conversations.',
|
||||
@@ -104,10 +209,14 @@ export const GroupAgentBuilderManifest: BuiltinToolManifest = {
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Update supervisor agent configuration (model, provider, plugins, etc.). Only include fields you want to update.',
|
||||
'Update agent configuration (model, provider, plugins, etc.). If agentId is not provided, updates the supervisor agent.',
|
||||
name: GroupAgentBuilderApiName.updateAgentConfig,
|
||||
parameters: {
|
||||
properties: {
|
||||
agentId: {
|
||||
description: 'The agent ID to update. If not provided, updates the supervisor agent.',
|
||||
type: 'string',
|
||||
},
|
||||
config: {
|
||||
description:
|
||||
'Partial agent configuration object. Only include fields you want to update.',
|
||||
@@ -139,7 +248,7 @@ export const GroupAgentBuilderManifest: BuiltinToolManifest = {
|
||||
type: 'object',
|
||||
},
|
||||
togglePlugin: {
|
||||
description: 'Toggle a specific plugin on/off for the supervisor agent.',
|
||||
description: 'Toggle a specific plugin on/off for the agent.',
|
||||
properties: {
|
||||
enabled: {
|
||||
description: 'Whether to enable (true) or disable (false) the plugin.',
|
||||
@@ -159,29 +268,27 @@ export const GroupAgentBuilderManifest: BuiltinToolManifest = {
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Update the group's system prompt. This is the instruction that defines how agents in the group should collaborate and interact.",
|
||||
name: GroupAgentBuilderApiName.updatePrompt,
|
||||
description: "Update a specific agent's system prompt (systemRole).",
|
||||
name: GroupAgentBuilderApiName.updateAgentPrompt,
|
||||
parameters: {
|
||||
properties: {
|
||||
prompt: {
|
||||
description: 'The new group system prompt content. Supports markdown formatting.',
|
||||
agentId: {
|
||||
description: 'The agent ID to update.',
|
||||
type: 'string',
|
||||
},
|
||||
streaming: {
|
||||
description:
|
||||
'Whether to use streaming mode for typewriter effect in the editor. Defaults to true.',
|
||||
type: 'boolean',
|
||||
prompt: {
|
||||
description: 'The new system prompt content. Supports markdown formatting.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['prompt'],
|
||||
required: ['agentId', 'prompt'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Update the group's configuration including opening message and opening questions. Use this to set the welcome experience when users start a new conversation with the group.",
|
||||
name: GroupAgentBuilderApiName.updateGroupConfig,
|
||||
"Update the group's configuration and metadata. Use this to customize the group's appearance and welcome experience.",
|
||||
name: GroupAgentBuilderApiName.updateGroup,
|
||||
parameters: {
|
||||
properties: {
|
||||
config: {
|
||||
@@ -202,8 +309,46 @@ export const GroupAgentBuilderManifest: BuiltinToolManifest = {
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
meta: {
|
||||
description: 'Partial metadata object. Only include fields you want to update.',
|
||||
properties: {
|
||||
avatar: {
|
||||
description: "An emoji or image URL for the group's avatar.",
|
||||
type: 'string',
|
||||
},
|
||||
backgroundColor: {
|
||||
description: 'Background color for the group avatar (hex color code).',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
description: 'A brief description of the group.',
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
description: 'The display name for the group.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
required: ['config'],
|
||||
required: [],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Update the group's shared prompt/content. This content is shared with all group members and defines the group's goals, workflow, or other shared information.",
|
||||
name: GroupAgentBuilderApiName.updateGroupPrompt,
|
||||
parameters: {
|
||||
properties: {
|
||||
prompt: {
|
||||
description:
|
||||
"The new shared prompt/content for the group. Supports markdown formatting. This content will be visible to all group members and helps define the group's working context.",
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['prompt'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,9 +11,9 @@ export const systemPrompt = `You are a Group Configuration Assistant integrated
|
||||
|
||||
The injected context includes:
|
||||
- **group_meta**: title, description
|
||||
- **group_config**: systemPrompt (group-level instruction), orchestratorModel, orchestratorProvider, responseOrder, responseSpeed
|
||||
- **group_config**: systemPrompt (group-level shared content)
|
||||
- **group_members**: List of agents in the group with their names, avatars, and roles (including the supervisor agent)
|
||||
- **supervisor_agent**: The supervisor agent's configuration (model, provider, plugins)
|
||||
- **supervisor_agent**: The supervisor agent's configuration (model, provider, plugins, systemRole)
|
||||
- **official_tools**: List of available official tools including built-in tools and Klavis integrations
|
||||
|
||||
You should use this context to understand the current state of the group and its members before making any modifications.
|
||||
@@ -23,7 +23,10 @@ You should use this context to understand the current state of the group and its
|
||||
You have access to tools that can modify group configurations:
|
||||
|
||||
**Group Member Management:**
|
||||
- **searchAgent**: Search for agents that can be invited to the group from the user's collection
|
||||
- **inviteAgent**: Invite an existing agent to join the group by their agent ID
|
||||
- **createAgent**: Create a new agent dynamically and add it to the group. **IMPORTANT**: Always include appropriate tools based on the agent's role.
|
||||
- **batchCreateAgents**: Create multiple agents at once and add them to the group. **IMPORTANT**: Each agent should have role-appropriate tools.
|
||||
- **removeAgent**: Remove an agent from the group (cannot remove the supervisor agent)
|
||||
|
||||
**Read Operations:**
|
||||
@@ -31,37 +34,151 @@ You have access to tools that can modify group configurations:
|
||||
- **searchMarketTools**: Search for tools (MCP plugins) in the marketplace for the supervisor agent
|
||||
|
||||
**Write Operations (for Group):**
|
||||
- **updatePrompt**: Update the group's system prompt (the instruction that guides how agents collaborate)
|
||||
- **updateGroupConfig**: Update group configuration including opening message and opening questions
|
||||
- **updateGroupPrompt**: Update the group's shared prompt (content shared by ALL group members)
|
||||
- **updateGroup**: Update group metadata and configuration including opening message and opening questions
|
||||
|
||||
**Write Operations (for Supervisor Agent):**
|
||||
- **updateConfig**: Update supervisor agent configuration (model, provider, plugins, etc.)
|
||||
- **togglePlugin**: Enable or disable a specific plugin for the supervisor agent
|
||||
**Write Operations (for Agent):**
|
||||
- **updateAgentPrompt**: Update any agent's system prompt (requires agentId). Can be used for both supervisor and member agents.
|
||||
- **updateConfig**: Update agent configuration (model, provider, plugins, etc.). If agentId is not provided, updates the supervisor agent.
|
||||
- **installPlugin**: Install and enable a plugin for the supervisor agent
|
||||
</capabilities>
|
||||
|
||||
<prompt_architecture>
|
||||
**IMPORTANT: There are TWO types of prompts in a group:**
|
||||
|
||||
1. **Group Prompt** (updated via \`updateGroupPrompt\`):
|
||||
- Shared content that ALL group members (including supervisor and sub-agents) can access
|
||||
- Contains background knowledge, project context, shared guidelines, or reference materials
|
||||
- **DO NOT include member information** - the system automatically injects group member details into the context
|
||||
- Think of this as a "shared document" or "knowledge base" for the entire group
|
||||
|
||||
2. **Agent Prompt** (updated via \`updateAgentPrompt\` with any agent's agentId):
|
||||
- The system role/instruction for a specific agent (can be supervisor OR any member agent)
|
||||
- For **supervisor agent**: defines orchestration logic, delegation strategy, coordination behavior
|
||||
- For **member agents**: defines their expertise, personality, response style, and capabilities
|
||||
- Each agent's prompt is private to that agent, NOT shared with other agents
|
||||
|
||||
**When to use which:**
|
||||
- User wants to add shared context/knowledge → use \`updateGroupPrompt\`
|
||||
- User wants to change how a specific agent behaves → use \`updateAgentPrompt\` with that agent's ID
|
||||
- User mentions "group prompt", "shared content", "background info" → use \`updateGroupPrompt\`
|
||||
- User mentions "agent behavior", "agent prompt", specific agent name → use \`updateAgentPrompt\`
|
||||
</prompt_architecture>
|
||||
|
||||
<supervisor_prompt_generation>
|
||||
**CRITICAL: Auto-generate Supervisor Prompt After Member Changes**
|
||||
|
||||
After ANY member change (createAgent, batchCreateAgents, inviteAgent, removeAgent), you MUST automatically update the supervisor's prompt. Use the following template structure:
|
||||
|
||||
**Supervisor Prompt Template:**
|
||||
\`\`\`
|
||||
You are the Supervisor of this group, responsible for coordinating and orchestrating conversations among team members.
|
||||
|
||||
## Orchestration Strategy
|
||||
|
||||
1. **Task Analysis**: When receiving a user request, first analyze what type of expertise is needed.
|
||||
|
||||
2. **Delegation Rules**:
|
||||
{Generate specific rules based on the actual members, for example:}
|
||||
- For coding/technical questions → delegate to [Developer Agent]
|
||||
- For design/UI discussions → delegate to [Designer Agent]
|
||||
- For general questions or coordination → handle yourself
|
||||
|
||||
3. **Collaboration Patterns**:
|
||||
- For complex tasks requiring multiple expertise → coordinate sequential or parallel involvement
|
||||
- Summarize and synthesize responses from multiple agents when needed
|
||||
|
||||
4. **Fallback Handling**:
|
||||
- If no specific agent fits → handle the request yourself
|
||||
- If clarification needed → ask the user before delegating
|
||||
|
||||
## Response Guidelines
|
||||
|
||||
- Always acknowledge which agent(s) will handle the request
|
||||
- Provide context when delegating to help the agent understand the task
|
||||
- Synthesize multi-agent responses into coherent answers for the user
|
||||
\`\`\`
|
||||
|
||||
**Generation Rules:**
|
||||
1. Analyze each member's title, description, and systemRole to understand their expertise
|
||||
2. Create specific delegation rules based on actual member capabilities
|
||||
3. Identify potential collaboration scenarios between members
|
||||
4. Keep the prompt concise but comprehensive
|
||||
5. Use the same language as the user's conversation
|
||||
</supervisor_prompt_generation>
|
||||
|
||||
<agent_tools_assignment>
|
||||
**CRITICAL: Assign Appropriate Tools When Creating Agents**
|
||||
|
||||
When creating agents (via \`createAgent\` or \`batchCreateAgents\`), you MUST analyze the agent's role and assign relevant tools from the \`official_tools\` context. Agents without proper tools cannot perform their specialized tasks effectively.
|
||||
|
||||
**Tool Assignment Strategy:**
|
||||
1. **Analyze the agent's role**: What tasks will this agent perform?
|
||||
2. **Match tools to capabilities**: Select tools that enable those tasks
|
||||
3. **Include the tools array**: Always specify the \`tools\` parameter with appropriate tool identifiers
|
||||
|
||||
**Common Tool Mappings (reference the actual \`official_tools\` context for available tools):**
|
||||
|
||||
| Agent Role | Recommended Tools | Rationale |
|
||||
|------------|-------------------|-----------|
|
||||
| Researcher / Analyst | web-crawler, search tools | Need to gather and analyze information |
|
||||
| Developer / Coder | lobe-cloud-sandbox, code execution tools | Need to write and run code |
|
||||
| Data Scientist | lobe-cloud-sandbox, data analysis tools | Need computational environment |
|
||||
| Writer / Editor | web-crawler (for research) | May need reference materials |
|
||||
| Financial / Trading | relevant MCP integrations, sandbox | Need market data and calculations |
|
||||
| Designer | image generation tools | Need to create visual assets |
|
||||
|
||||
**Example - Quant Trading Team:**
|
||||
- **Quant Researcher**: tools: ["web-crawler", "lobe-cloud-sandbox"] - for market research and data analysis
|
||||
- **Execution Specialist**: tools: ["trading-mcp", "lobe-cloud-sandbox"] - for executing trades and backtesting
|
||||
- **Risk Manager**: tools: ["lobe-cloud-sandbox"] - for risk calculations
|
||||
|
||||
**Rules:**
|
||||
1. NEVER create an agent without considering what tools it needs
|
||||
2. Reference \`official_tools\` in the context to see available tool identifiers
|
||||
3. If a specialized tool doesn't exist, note this limitation to the user
|
||||
4. Tools enable agent capabilities - an agent without tools is limited to conversation only
|
||||
</agent_tools_assignment>
|
||||
|
||||
<workflow>
|
||||
1. **Understand the request**: Listen carefully to what the user wants to configure
|
||||
2. **Reference injected context**: Use the \`<current_group_context>\` to understand current state - no need to call read APIs
|
||||
3. **Make targeted changes**: Use the appropriate API based on whether you're modifying the group or the supervisor agent
|
||||
4. **Confirm changes**: Report what was changed and the new values
|
||||
3. **Distinguish prompt types**: Determine if the user wants to modify shared content (group prompt) or a specific agent's behavior (agent prompt)
|
||||
4. **Make targeted changes**: Use the appropriate API based on whether you're modifying the group or a specific agent
|
||||
5. **Update supervisor prompt after member changes**: **IMPORTANT** - After ANY member change (create, invite, or remove agent), you MUST automatically update the supervisor's prompt using \`updateAgentPrompt\` with the supervisor's agentId. Generate an appropriate orchestration prompt based on the current members.
|
||||
6. **Confirm changes**: Report what was changed and the new values
|
||||
</workflow>
|
||||
|
||||
<guidelines>
|
||||
1. **Use injected context**: The current group's config and member list are already available. Reference them directly instead of calling read APIs.
|
||||
2. **Distinguish group vs agent operations**:
|
||||
- Group-level: updatePrompt (group systemPrompt), inviteAgent, removeAgent
|
||||
- Supervisor agent-level: updateConfig, togglePlugin, installPlugin (for model, plugins, etc.)
|
||||
3. **Explain your changes**: When modifying configurations, explain what you're changing and why it might benefit the group collaboration.
|
||||
4. **Validate user intent**: For significant changes (like removing an agent), confirm with the user before proceeding.
|
||||
5. **Provide recommendations**: When users ask for advice, consider how changes affect multi-agent collaboration.
|
||||
6. **Use user's language**: Always respond in the same language the user is using.
|
||||
7. **Cannot remove supervisor**: The supervisor agent cannot be removed from the group - it's the orchestrator.
|
||||
2. **Distinguish group vs agent prompts**:
|
||||
- Group prompt: Shared content for all members, NO member info needed (auto-injected)
|
||||
- Agent prompt: Individual agent's system role (supervisor or member), requires agentId
|
||||
3. **Distinguish group vs agent operations**:
|
||||
- Group-level: updateGroupPrompt, updateGroup, inviteAgent, removeAgent, batchCreateAgents
|
||||
- Agent-level: updateAgentPrompt (requires agentId), updateConfig (agentId optional, defaults to supervisor), installPlugin
|
||||
4. **CRITICAL - Auto-update supervisor after member changes**: After ANY member change (create, invite, remove), you MUST automatically call \`updateAgentPrompt\` with supervisor's agentId to regenerate the orchestration prompt. This is NOT optional - the supervisor needs updated delegation rules to coordinate the team effectively.
|
||||
5. **CRITICAL - Assign tools when creating agents**: When using \`createAgent\` or \`batchCreateAgents\`, ALWAYS include appropriate \`tools\` based on the agent's role. Reference \`official_tools\` in the context for available tool identifiers. An agent without proper tools cannot perform specialized tasks.
|
||||
6. **Explain your changes**: When modifying configurations, explain what you're changing and why it might benefit the group collaboration.
|
||||
7. **Validate user intent**: For significant changes (like removing an agent), confirm with the user before proceeding.
|
||||
8. **Provide recommendations**: When users ask for advice, consider how changes affect multi-agent collaboration.
|
||||
9. **Use user's language**: Always respond in the same language the user is using.
|
||||
10. **Cannot remove supervisor**: The supervisor agent cannot be removed from the group - it's the orchestrator.
|
||||
</guidelines>
|
||||
|
||||
<configuration_knowledge>
|
||||
**Group Prompt (Shared Content):**
|
||||
- Content that all group members can access and reference
|
||||
- Suitable for: project background, domain knowledge, shared guidelines, reference materials
|
||||
- NOT for: member lists (auto-injected), coordination rules (use agent prompt)
|
||||
|
||||
**Agent Prompt (via updateAgentPrompt with agentId):**
|
||||
- Updates any agent's system prompt - both supervisor and member agents
|
||||
- **Supervisor agent**: defines orchestration logic, delegation strategy, coordination behavior
|
||||
- **Member agents**: defines their expertise, personality, response style, and capabilities
|
||||
- Each agent's prompt is private to that agent
|
||||
|
||||
**Group Configuration:**
|
||||
- systemPrompt: The group-level instruction that defines how agents should collaborate and interact
|
||||
- orchestratorModel: The model used for orchestrating multi-agent conversations
|
||||
- orchestratorProvider: The provider for the orchestrator model
|
||||
- responseOrder: How agents respond ("sequential" or "natural")
|
||||
@@ -69,64 +186,133 @@ You have access to tools that can modify group configurations:
|
||||
- openingMessage: The welcome message shown when starting a new conversation with the group
|
||||
- openingQuestions: Suggested questions to help users get started with the group conversation
|
||||
|
||||
**Supervisor Agent Configuration:**
|
||||
- model: The AI model for the supervisor agent
|
||||
**Agent Configuration (via updateConfig):**
|
||||
- model: The AI model for the agent
|
||||
- provider: The AI provider
|
||||
- plugins: Tools enabled for the supervisor agent
|
||||
- The supervisor orchestrates the conversation and coordinates other agents
|
||||
- plugins: Tools enabled for the agent
|
||||
- If agentId is not provided, updates the supervisor agent by default
|
||||
|
||||
**Group Members:**
|
||||
- Each group has one supervisor agent and zero or more member agents
|
||||
- Member agents can be invited or removed
|
||||
- The supervisor agent cannot be removed (it's essential for group coordination)
|
||||
|
||||
**System Prompt vs Agent Prompt:**
|
||||
- Group systemPrompt: Defines collaboration rules for the entire group
|
||||
- Agent systemRole: Individual agent's personality and expertise (not modified here)
|
||||
</configuration_knowledge>
|
||||
|
||||
<examples>
|
||||
User: "帮我邀请一个 Agent 到群组"
|
||||
Action: Ask which agent they want to invite (need the agent ID), then use inviteAgent
|
||||
<example>
|
||||
User: "Invite an agent to the group"
|
||||
Action:
|
||||
1. Use searchAgent to find available agents, show the results to user
|
||||
2. Use inviteAgent with the selected agent ID
|
||||
3. **Then automatically** use updateAgentPrompt with supervisor's agentId to update orchestration prompt with the newly invited agent's delegation rules
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Add a developer agent to help with coding"
|
||||
Action:
|
||||
1. Use searchAgent with query "developer" or "coding" to find relevant agents
|
||||
2. Use inviteAgent or createAgent if no suitable agent exists. If creating, include tools: ["lobe-cloud-sandbox"] for code execution
|
||||
3. **Then automatically** use updateAgentPrompt with supervisor's agentId to update orchestration prompt with the new developer agent's delegation rules
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Create a marketing expert for this group"
|
||||
Action:
|
||||
1. Use createAgent with title "Marketing Expert", appropriate systemRole, description, and tools: ["web-crawler"] for research capabilities
|
||||
2. **Then automatically** use updateAgentPrompt with supervisor's agentId to update orchestration prompt, adding delegation rules for marketing-related tasks
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Create 3 expert agents for me"
|
||||
Action:
|
||||
1. Use batchCreateAgents to create multiple agents at once with their respective titles, systemRoles, descriptions, and **appropriate tools for each agent's role**
|
||||
2. **Then automatically** use updateAgentPrompt with supervisor's agentId to generate orchestration prompt that includes delegation rules for all 3 new experts
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Create a quant trading team"
|
||||
Action:
|
||||
1. Use batchCreateAgents with agents like:
|
||||
- Quant Researcher: tools: ["web-crawler", "lobe-cloud-sandbox"] for market research and data analysis
|
||||
- Execution Specialist: tools: ["lobe-cloud-sandbox"] for backtesting and trade simulation (note: if specific trading MCP is needed, check official_tools or recommend installing one)
|
||||
- Risk Manager: tools: ["lobe-cloud-sandbox"] for risk calculations
|
||||
2. **Then automatically** use updateAgentPrompt with supervisor's agentId to generate orchestration prompt with delegation rules for quant workflows
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Remove the coding assistant from the group"
|
||||
Action: Check the group members in context, find the agent ID for "coding assistant", then use removeAgent
|
||||
Action:
|
||||
1. Check the group members in context, find the agent ID for "coding assistant"
|
||||
2. Use removeAgent to remove the agent
|
||||
3. **Then automatically** use updateAgentPrompt with supervisor's agentId to update orchestration prompt, removing the delegation rules for the removed agent
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "What agents are in this group?"
|
||||
Action: Reference the \`<group_members>\` from the injected context and display the list
|
||||
</example>
|
||||
|
||||
User: "Change the group's system prompt to encourage more collaboration"
|
||||
Action: Reference the current systemPrompt from context, then use updatePrompt to update it
|
||||
<example>
|
||||
User: "Add some background information about our project to the group"
|
||||
Action: Use updateGroupPrompt to add the project context as shared content for all members
|
||||
</example>
|
||||
|
||||
User: "帮我把主持人的模型改成 Claude"
|
||||
<example>
|
||||
User: "Update the group's shared knowledge base"
|
||||
Action: Use updateGroupPrompt - this is shared content, do NOT include member information (auto-injected)
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Change how the supervisor coordinates the team"
|
||||
Action: Use updateAgentPrompt with the supervisor's agentId to update orchestration logic
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Make the supervisor more proactive in assigning tasks"
|
||||
Action: Use updateAgentPrompt with supervisor's agentId to update coordination strategy
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Update the coding assistant's prompt to focus more on Python"
|
||||
Action: Find the coding assistant's agentId from group_members context, then use updateAgentPrompt with that agentId
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Modify the designer agent's prompt"
|
||||
Action: Find the designer agent's agentId from group_members context, then use updateAgentPrompt with that agentId
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Change the supervisor's model to Claude"
|
||||
Action: Use updateConfig with { config: { model: "claude-sonnet-4-5-20250929", provider: "anthropic" } } for the supervisor agent
|
||||
</example>
|
||||
|
||||
User: "Enable web browsing for the supervisor"
|
||||
Action: Use togglePlugin with pluginId "lobe-web-browsing" and enabled: true
|
||||
|
||||
<example>
|
||||
User: "What can the supervisor agent do?"
|
||||
Action: Reference the \`<supervisor_agent>\` config from the context, including model, plugins, etc.
|
||||
Action: Reference the \`<supervisor_agent>\` config from the context, including model, tools, etc.
|
||||
</example>
|
||||
|
||||
User: "Set the response order to sequential"
|
||||
Action: This is a group-level config, use updatePrompt or mention this needs to be changed in group settings
|
||||
|
||||
User: "帮我添加一些新的工具给这个群组"
|
||||
<example>
|
||||
User: "Add some new tools to this group"
|
||||
Action: Use searchMarketTools to find tools, then use installPlugin for the supervisor agent
|
||||
</example>
|
||||
|
||||
<example>
|
||||
User: "Set a welcome message for this group"
|
||||
Action: Use updateGroupConfig with { config: { openingMessage: "Welcome to the team! We're here to help you with your project." } }
|
||||
Action: Use updateGroup with { config: { openingMessage: "Welcome to the team! We're here to help you with your project." } }
|
||||
</example>
|
||||
|
||||
User: "帮我设置一些开场问题"
|
||||
Action: Use updateGroupConfig with { config: { openingQuestions: ["What project are you working on?", "How can we help you today?", "Do you have any specific questions?"] } }
|
||||
|
||||
User: "Remove the opening message"
|
||||
Action: Use updateGroupConfig with { config: { openingMessage: "" } }
|
||||
<example>
|
||||
User: "Set some opening questions"
|
||||
Action: Use updateGroup with { config: { openingQuestions: ["What project are you working on?", "How can we help you today?", "Do you have any specific questions?"] } }
|
||||
</example>
|
||||
</examples>
|
||||
|
||||
<response_format>
|
||||
- When showing configuration, format it in a clear, readable way using markdown
|
||||
- When making changes, clearly state what was changed (before → after)
|
||||
- Distinguish between group-level and agent-level changes
|
||||
- Clarify whether you're updating shared content (group prompt) or a specific agent's prompt
|
||||
- Use bullet points for listing multiple items
|
||||
- Keep responses concise but informative
|
||||
</response_format>`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { UpdateAgentConfigParams } from '@lobechat/builtin-tool-agent-builder';
|
||||
import type { MetaData } from '@lobechat/types';
|
||||
|
||||
/**
|
||||
@@ -9,22 +10,29 @@ export const GroupAgentBuilderIdentifier = 'lobe-group-agent-builder';
|
||||
* Group Agent Builder API Names
|
||||
*/
|
||||
export const GroupAgentBuilderApiName = {
|
||||
// Group member management operations
|
||||
batchCreateAgents: 'batchCreateAgents',
|
||||
createAgent: 'createAgent',
|
||||
|
||||
// Read operations (inherited from AgentBuilder)
|
||||
getAvailableModels: 'getAvailableModels',
|
||||
|
||||
// Write operations (inherited from AgentBuilder)
|
||||
installPlugin: 'installPlugin',
|
||||
|
||||
// Group-specific operations
|
||||
inviteAgent: 'inviteAgent',
|
||||
|
||||
removeAgent: 'removeAgent',
|
||||
|
||||
searchAgent: 'searchAgent',
|
||||
|
||||
searchMarketTools: 'searchMarketTools',
|
||||
|
||||
updateAgentConfig: 'updateConfig',
|
||||
// Group config operations
|
||||
updateGroupConfig: 'updateGroupConfig',
|
||||
updatePrompt: 'updatePrompt',
|
||||
// Group operations
|
||||
updateAgentPrompt: 'updateAgentPrompt',
|
||||
updateGroup: 'updateGroup',
|
||||
updateGroupPrompt: 'updateGroupPrompt',
|
||||
} as const;
|
||||
|
||||
export type GroupAgentBuilderApiNameType =
|
||||
@@ -32,6 +40,41 @@ export type GroupAgentBuilderApiNameType =
|
||||
|
||||
// ============== Group-specific Parameter Types ==============
|
||||
|
||||
export interface SearchAgentParams {
|
||||
/**
|
||||
* Maximum number of results to return
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* Search query to find agents by name or description
|
||||
*/
|
||||
query?: string;
|
||||
}
|
||||
|
||||
export interface CreateAgentParams {
|
||||
/**
|
||||
* An emoji or image URL for the agent's avatar
|
||||
*/
|
||||
avatar?: string;
|
||||
/**
|
||||
* A brief description of what this agent does
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* The system prompt that defines the agent's behavior
|
||||
*/
|
||||
systemRole: string;
|
||||
/**
|
||||
* The display name for the new agent
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* List of tool identifiers to enable for this agent.
|
||||
* Use the same identifiers as shown in official_tools context.
|
||||
*/
|
||||
tools?: string[];
|
||||
}
|
||||
|
||||
export interface InviteAgentParams {
|
||||
/**
|
||||
* Agent identifier to invite to the group
|
||||
@@ -46,9 +89,92 @@ export interface RemoveAgentParams {
|
||||
agentId: string;
|
||||
}
|
||||
|
||||
export interface UpdateAgentPromptParams {
|
||||
/**
|
||||
* The agent ID to update
|
||||
*/
|
||||
agentId: string;
|
||||
/**
|
||||
* The new system prompt content (markdown format)
|
||||
*/
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
export interface UpdateAgentPromptState {
|
||||
/**
|
||||
* The agent ID that was updated
|
||||
*/
|
||||
agentId: string;
|
||||
/**
|
||||
* The new prompt
|
||||
*/
|
||||
newPrompt: string;
|
||||
/**
|
||||
* The previous prompt
|
||||
*/
|
||||
previousPrompt?: string;
|
||||
/**
|
||||
* Whether the operation was successful
|
||||
*/
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended UpdateAgentConfigParams with optional agentId for group context
|
||||
*/
|
||||
export interface UpdateAgentConfigWithIdParams extends UpdateAgentConfigParams {
|
||||
/**
|
||||
* The agent ID to update. If not provided, updates the supervisor agent.
|
||||
*/
|
||||
agentId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified params for updating group (combines config and meta)
|
||||
*/
|
||||
export interface UpdateGroupParams {
|
||||
/**
|
||||
* Partial group configuration to update
|
||||
*/
|
||||
config?: {
|
||||
/**
|
||||
* Opening message shown when starting a new conversation with the group
|
||||
*/
|
||||
openingMessage?: string;
|
||||
/**
|
||||
* Suggested opening questions to help users get started
|
||||
*/
|
||||
openingQuestions?: string[];
|
||||
};
|
||||
/**
|
||||
* Partial metadata to update for the group
|
||||
*/
|
||||
meta?: Partial<Pick<MetaData, 'avatar' | 'backgroundColor' | 'description' | 'tags' | 'title'>>;
|
||||
}
|
||||
|
||||
export interface UpdateGroupState {
|
||||
/**
|
||||
* Whether the operation was successful
|
||||
*/
|
||||
success: boolean;
|
||||
/**
|
||||
* The updated configuration values
|
||||
*/
|
||||
updatedConfig?: {
|
||||
openingMessage?: string;
|
||||
openingQuestions?: string[];
|
||||
};
|
||||
/**
|
||||
* The updated metadata values
|
||||
*/
|
||||
updatedMeta?: Partial<
|
||||
Pick<MetaData, 'avatar' | 'backgroundColor' | 'description' | 'tags' | 'title'>
|
||||
>;
|
||||
}
|
||||
|
||||
export interface UpdateGroupPromptParams {
|
||||
/**
|
||||
* The new system prompt content for the group (markdown format)
|
||||
* The new shared prompt/content for the group (markdown format)
|
||||
*/
|
||||
prompt: string;
|
||||
/**
|
||||
@@ -57,15 +183,77 @@ export interface UpdateGroupPromptParams {
|
||||
streaming?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateGroupMetaParams {
|
||||
export interface UpdateGroupPromptState {
|
||||
/**
|
||||
* Partial metadata to update for the group
|
||||
* The new prompt
|
||||
*/
|
||||
meta?: Partial<Pick<MetaData, 'title' | 'description' | 'avatar' | 'backgroundColor' | 'tags'>>;
|
||||
newPrompt: string;
|
||||
/**
|
||||
* The previous prompt
|
||||
*/
|
||||
previousPrompt?: string;
|
||||
/**
|
||||
* Whether the operation was successful
|
||||
*/
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface BatchCreateAgentsParams {
|
||||
/**
|
||||
* Array of agents to create
|
||||
*/
|
||||
agents: CreateAgentParams[];
|
||||
}
|
||||
|
||||
export interface BatchCreateAgentsState {
|
||||
/**
|
||||
* Created agents info
|
||||
*/
|
||||
agents: Array<{
|
||||
agentId: string;
|
||||
success: boolean;
|
||||
title: string;
|
||||
}>;
|
||||
/**
|
||||
* Number of agents that failed to create
|
||||
*/
|
||||
failedCount: number;
|
||||
/**
|
||||
* Number of agents successfully created
|
||||
*/
|
||||
successCount: number;
|
||||
}
|
||||
|
||||
// ============== State Types (for Render components) ==============
|
||||
|
||||
export interface SearchAgentResult {
|
||||
avatar?: string;
|
||||
description?: string;
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface SearchAgentState {
|
||||
agents: SearchAgentResult[];
|
||||
query?: string;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface CreateAgentState {
|
||||
/**
|
||||
* The ID of the created agent
|
||||
*/
|
||||
agentId: string;
|
||||
/**
|
||||
* Whether the operation was successful
|
||||
*/
|
||||
success: boolean;
|
||||
/**
|
||||
* The title of the created agent
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface InviteAgentState {
|
||||
/**
|
||||
* Agent identifier that was invited
|
||||
@@ -95,41 +283,3 @@ export interface RemoveAgentState {
|
||||
*/
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateGroupPromptState {
|
||||
newPrompt: string;
|
||||
previousPrompt?: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// ============== Group Config Types ==============
|
||||
|
||||
export interface UpdateGroupConfigParams {
|
||||
/**
|
||||
* Partial group configuration to update
|
||||
*/
|
||||
config?: {
|
||||
/**
|
||||
* Opening message shown when starting a new conversation with the group
|
||||
*/
|
||||
openingMessage?: string;
|
||||
/**
|
||||
* Suggested opening questions to help users get started
|
||||
*/
|
||||
openingQuestions?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateGroupConfigState {
|
||||
/**
|
||||
* Whether the operation was successful
|
||||
*/
|
||||
success: boolean;
|
||||
/**
|
||||
* The updated configuration values
|
||||
*/
|
||||
updatedConfig: {
|
||||
openingMessage?: string;
|
||||
openingQuestions?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@lobechat/const';
|
||||
import type { AgentItem, BuiltinInspectorProps } from '@lobechat/types';
|
||||
import type { AgentGroupMember, BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx, useTheme } from 'antd-style';
|
||||
import { memo, useMemo } from 'react';
|
||||
@@ -45,7 +45,7 @@ export const BroadcastInspector = memo<BuiltinInspectorProps<BroadcastParams>>(
|
||||
if (!agentIds.length || !groupAgents.length) return [];
|
||||
return agentIds
|
||||
.map((id) => groupAgents.find((agent) => agent.id === id))
|
||||
.filter((agent): agent is AgentItem => !!agent);
|
||||
.filter((agent): agent is AgentGroupMember => !!agent);
|
||||
}, [agentIds, groupAgents]);
|
||||
|
||||
// Transform agents to Avatar.Group format
|
||||
|
||||
@@ -57,7 +57,7 @@ export const GroupManagementManifest: BuiltinToolManifest = {
|
||||
{
|
||||
description:
|
||||
'Create a new agent dynamically based on user requirements and add it to the group. Use this when no existing agent matches the needed expertise.',
|
||||
humanIntervention: 'always',
|
||||
humanIntervention: 'required',
|
||||
name: GroupManagementApiName.createAgent,
|
||||
parameters: {
|
||||
properties: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createStaticStyles, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { ClearTodosParams, ClearTodosState } from '../../../types';
|
||||
|
||||
@@ -15,14 +15,6 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
color: ${cssVar.colorText};
|
||||
background: linear-gradient(to top, ${cssVar.colorWarningBg} 40%, transparent 40%);
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const ClearTodosInspector = memo<BuiltinInspectorProps<ClearTodosParams, ClearTodosState>>(
|
||||
@@ -33,7 +25,7 @@ export const ClearTodosInspector = memo<BuiltinInspectorProps<ClearTodosParams,
|
||||
|
||||
if (isArgumentsStreaming && !mode) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.clearTodos')}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -45,7 +37,9 @@ export const ClearTodosInspector = memo<BuiltinInspectorProps<ClearTodosParams,
|
||||
: t('builtins.lobe-gtd.apiName.clearTodos.modeCompleted');
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div
|
||||
className={cx(inspectorTextStyles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
>
|
||||
<Trans
|
||||
components={{ mode: <span className={styles.mode} /> }}
|
||||
i18nKey="builtins.lobe-gtd.apiName.clearTodos.result"
|
||||
|
||||
@@ -7,17 +7,11 @@ import { CheckCircle } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { oneLineEllipsis, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { CompleteTodosParams, CompleteTodosState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
`,
|
||||
title: css`
|
||||
margin-inline-end: 8px;
|
||||
color: ${cssVar.colorText};
|
||||
@@ -34,14 +28,14 @@ export const CompleteTodosInspector = memo<
|
||||
|
||||
if (isArgumentsStreaming && count === 0) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.completeTodos')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<span className={styles.title}>{t('builtins.lobe-gtd.apiName.completeTodos')}</span>
|
||||
{count > 0 && (
|
||||
<Text as={'span'} code color={cssVar.colorSuccess} fontSize={12}>
|
||||
|
||||
@@ -1,25 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { CreatePlanParams, CreatePlanState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const CreatePlanInspector = memo<BuiltinInspectorProps<CreatePlanParams, CreatePlanState>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
@@ -28,14 +17,16 @@ export const CreatePlanInspector = memo<BuiltinInspectorProps<CreatePlanParams,
|
||||
|
||||
if (isArgumentsStreaming && !goal) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.createPlan')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div
|
||||
className={cx(inspectorTextStyles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
>
|
||||
{goal ? (
|
||||
<Trans
|
||||
components={{ goal: <span className={highlightTextStyles.primary} /> }}
|
||||
|
||||
@@ -7,17 +7,11 @@ import { Plus } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { oneLineEllipsis, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { CreateTodosParams, CreateTodosState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
`,
|
||||
title: css`
|
||||
margin-inline-end: 8px;
|
||||
color: ${cssVar.colorText};
|
||||
@@ -35,14 +29,14 @@ export const CreateTodosInspector = memo<
|
||||
|
||||
if (isArgumentsStreaming && count === 0) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.createTodos')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<span className={styles.title}>{t('builtins.lobe-gtd.apiName.createTodos')}</span>
|
||||
{count > 0 && (
|
||||
<Text as={'span'} code color={cssVar.colorSuccess} fontSize={12}>
|
||||
|
||||
@@ -1,25 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { ExecTaskParams, ExecTaskState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const ExecTaskInspector = memo<BuiltinInspectorProps<ExecTaskParams, ExecTaskState>>(
|
||||
({ args, partialArgs, isArgumentsStreaming, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
@@ -30,13 +19,13 @@ export const ExecTaskInspector = memo<BuiltinInspectorProps<ExecTaskParams, Exec
|
||||
if (isArgumentsStreaming) {
|
||||
if (!description)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.execTask')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.execTask.loading')}</span>
|
||||
<span className={highlightTextStyles.primary}>{description}</span>
|
||||
</div>
|
||||
@@ -46,7 +35,7 @@ export const ExecTaskInspector = memo<BuiltinInspectorProps<ExecTaskParams, Exec
|
||||
// 有 description 时,根据 loading 状态显示不同文案
|
||||
if (description) {
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span>
|
||||
{isLoading
|
||||
? t('builtins.lobe-gtd.apiName.execTask.loading')
|
||||
@@ -59,7 +48,7 @@ export const ExecTaskInspector = memo<BuiltinInspectorProps<ExecTaskParams, Exec
|
||||
|
||||
// fallback
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={inspectorTextStyles.root}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.execTask')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,17 +7,11 @@ import { Minus } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { oneLineEllipsis, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { RemoveTodosParams, RemoveTodosState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
`,
|
||||
title: css`
|
||||
margin-inline-end: 8px;
|
||||
color: ${cssVar.colorText};
|
||||
@@ -34,14 +28,14 @@ export const RemoveTodosInspector = memo<
|
||||
|
||||
if (isArgumentsStreaming && count === 0) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.removeTodos')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<span className={styles.title}>{t('builtins.lobe-gtd.apiName.removeTodos')}</span>
|
||||
{count > 0 && (
|
||||
<Text as={'span'} code color={cssVar.colorError} fontSize={12}>
|
||||
|
||||
@@ -7,17 +7,11 @@ import { CheckCircle, DiffIcon } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { oneLineEllipsis, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { UpdatePlanParams, UpdatePlanState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
`,
|
||||
title: css`
|
||||
margin-inline-end: 8px;
|
||||
color: ${cssVar.colorText};
|
||||
@@ -34,14 +28,14 @@ export const UpdatePlanInspector = memo<BuiltinInspectorProps<UpdatePlanParams,
|
||||
|
||||
if (isArgumentsStreaming && !planId) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.updatePlan')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<span className={styles.title}>{t('builtins.lobe-gtd.apiName.updatePlan')}</span>
|
||||
{completed && (
|
||||
<Text as={'span'} code color={cssVar.colorSuccess} fontSize={12}>
|
||||
|
||||
@@ -7,17 +7,11 @@ import { CheckCircle, DiffIcon, Minus, Plus } from 'lucide-react';
|
||||
import { type ReactNode, memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { oneLineEllipsis, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { UpdateTodosParams, UpdateTodosState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
`,
|
||||
separator: css`
|
||||
margin-inline: 2px;
|
||||
color: ${cssVar.colorTextQuaternary};
|
||||
@@ -66,7 +60,7 @@ export const UpdateTodosInspector = memo<
|
||||
|
||||
if (isArgumentsStreaming && !hasOperations) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-gtd.apiName.updateTodos')}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -107,7 +101,7 @@ export const UpdateTodosInspector = memo<
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<span className={styles.title}>{t('builtins.lobe-gtd.apiName.updateTodos')}</span>
|
||||
{statsParts.length > 0 && (
|
||||
<>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createStaticStyles, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type ReadKnowledgeArgs, type ReadKnowledgeState } from '../../..';
|
||||
|
||||
@@ -14,18 +14,6 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
margin-inline-start: 4px;
|
||||
color: ${cssVar.colorTextTertiary};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const ReadKnowledgeInspector = memo<
|
||||
@@ -43,13 +31,13 @@ export const ReadKnowledgeInspector = memo<
|
||||
if (isArgumentsStreaming) {
|
||||
if (fileCount === 0)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-knowledge-base.apiName.readKnowledge')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-knowledge-base.apiName.readKnowledge')}: </span>
|
||||
<span className={highlightTextStyles.gold}>
|
||||
{fileCount} {fileCount === 1 ? 'file' : 'files'}
|
||||
@@ -85,7 +73,7 @@ export const ReadKnowledgeInspector = memo<
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span style={{ marginInlineStart: 2 }}>
|
||||
<span>{t('builtins.lobe-knowledge-base.apiName.readKnowledge')}: </span>
|
||||
{renderFileInfo()}
|
||||
|
||||
+5
-16
@@ -2,25 +2,14 @@
|
||||
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { cssVar, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type SearchKnowledgeBaseArgs, type SearchKnowledgeBaseState } from '../../..';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const SearchKnowledgeBaseInspector = memo<
|
||||
BuiltinInspectorProps<SearchKnowledgeBaseArgs, SearchKnowledgeBaseState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
@@ -35,13 +24,13 @@ export const SearchKnowledgeBaseInspector = memo<
|
||||
if (isArgumentsStreaming) {
|
||||
if (!query)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-knowledge-base.apiName.searchKnowledgeBase')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-knowledge-base.apiName.searchKnowledgeBase')}: </span>
|
||||
<span className={highlightTextStyles.gold}>{query}</span>
|
||||
</div>
|
||||
@@ -49,7 +38,7 @@ export const SearchKnowledgeBaseInspector = memo<
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span style={{ marginInlineStart: 2 }}>
|
||||
<span>{t('builtins.lobe-knowledge-base.apiName.searchKnowledgeBase')}: </span>
|
||||
{query && <span className={highlightTextStyles.gold}>{query}</span>}
|
||||
|
||||
@@ -8,20 +8,12 @@ import { Minus, Plus } from 'lucide-react';
|
||||
import { type ReactNode, memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type EditLocalFileState } from '../../../types';
|
||||
import { FilePathDisplay } from '../../components/FilePathDisplay';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
separator: css`
|
||||
margin-inline: 2px;
|
||||
color: ${cssVar.colorTextQuaternary};
|
||||
@@ -39,13 +31,13 @@ export const EditLocalFileInspector = memo<
|
||||
if (isArgumentsStreaming) {
|
||||
if (!filePath)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.editLocalFile')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.editLocalFile')}: </span>
|
||||
<FilePathDisplay filePath={filePath} />
|
||||
</div>
|
||||
@@ -75,7 +67,7 @@ export const EditLocalFileInspector = memo<
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.editLocalFile')}: </span>
|
||||
<FilePathDisplay filePath={filePath} />
|
||||
{!isLoading && statsParts.length > 0 && (
|
||||
|
||||
@@ -7,19 +7,11 @@ import { Check, X } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type GlobFilesState } from '../../..';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
@@ -36,13 +28,13 @@ export const GlobLocalFilesInspector = memo<BuiltinInspectorProps<GlobFilesParam
|
||||
if (isArgumentsStreaming) {
|
||||
if (!pattern)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.globLocalFiles')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.globLocalFiles')}: </span>
|
||||
<span className={highlightTextStyles.primary}>{pattern}</span>
|
||||
</div>
|
||||
@@ -53,7 +45,7 @@ export const GlobLocalFilesInspector = memo<BuiltinInspectorProps<GlobFilesParam
|
||||
const isSuccess = pluginState?.result?.success;
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span style={{ marginInlineStart: 2 }}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.globLocalFiles')}: </span>
|
||||
{pattern && <span className={highlightTextStyles.primary}>{pattern}</span>}
|
||||
|
||||
@@ -3,25 +3,14 @@
|
||||
import { type GrepContentParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { cssVar, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type GrepContentState } from '../../..';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const GrepContentInspector = memo<
|
||||
BuiltinInspectorProps<GrepContentParams, GrepContentState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, pluginState, isLoading }) => {
|
||||
@@ -33,13 +22,13 @@ export const GrepContentInspector = memo<
|
||||
if (isArgumentsStreaming) {
|
||||
if (!pattern)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.grepContent')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.grepContent')}: </span>
|
||||
<span className={highlightTextStyles.primary}>{pattern}</span>
|
||||
</div>
|
||||
@@ -51,7 +40,7 @@ export const GrepContentInspector = memo<
|
||||
const hasResults = resultCount > 0;
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.grepContent')}: </span>
|
||||
{pattern && <span className={highlightTextStyles.primary}>{pattern}</span>}
|
||||
{!isLoading &&
|
||||
|
||||
@@ -3,26 +3,15 @@
|
||||
import { type ListLocalFileParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { cssVar, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type LocalFileListState } from '../../..';
|
||||
import { FilePathDisplay } from '../../components/FilePathDisplay';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const ListLocalFilesInspector = memo<
|
||||
BuiltinInspectorProps<ListLocalFileParams, LocalFileListState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, pluginState, isLoading }) => {
|
||||
@@ -34,13 +23,13 @@ export const ListLocalFilesInspector = memo<
|
||||
if (isArgumentsStreaming) {
|
||||
if (!path)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.listLocalFiles')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.listLocalFiles')}: </span>
|
||||
<FilePathDisplay filePath={path} isDirectory />
|
||||
</div>
|
||||
@@ -52,7 +41,7 @@ export const ListLocalFilesInspector = memo<
|
||||
const hasResults = resultCount > 0;
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.listLocalFiles')}: </span>
|
||||
<FilePathDisplay filePath={path} isDirectory />
|
||||
{!isLoading &&
|
||||
|
||||
@@ -2,26 +2,15 @@
|
||||
|
||||
import { type LocalReadFileParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type LocalReadFileState } from '../../..';
|
||||
import { FilePathDisplay } from '../../components/FilePathDisplay';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const ReadLocalFileInspector = memo<
|
||||
BuiltinInspectorProps<LocalReadFileParams, LocalReadFileState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading }) => {
|
||||
@@ -33,13 +22,13 @@ export const ReadLocalFileInspector = memo<
|
||||
if (isArgumentsStreaming) {
|
||||
if (!filePath)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.readLocalFile')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.readLocalFile')}: </span>
|
||||
<FilePathDisplay filePath={filePath} />
|
||||
</div>
|
||||
@@ -47,7 +36,7 @@ export const ReadLocalFileInspector = memo<
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.readLocalFile')}: </span>
|
||||
<FilePathDisplay filePath={filePath} />
|
||||
</div>
|
||||
|
||||
@@ -8,23 +8,15 @@ import path from 'path-browserify-esm';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type LocalRenameFileState } from '../../..';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
icon: css`
|
||||
flex-shrink: 0;
|
||||
margin-inline-end: 4px;
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const RenameLocalFileInspector = memo<
|
||||
@@ -39,7 +31,9 @@ export const RenameLocalFileInspector = memo<
|
||||
const oldName = filePath ? path.basename(filePath) : '';
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div
|
||||
className={cx(inspectorTextStyles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
>
|
||||
{oldName && newName ? (
|
||||
<>
|
||||
{t('builtins.lobe-local-system.apiName.renameLocalFile')} {oldName} →{' '}
|
||||
|
||||
@@ -7,17 +7,9 @@ import { Check, X } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
statusIcon: css`
|
||||
margin-block-end: -2px;
|
||||
margin-inline-start: 4px;
|
||||
@@ -40,13 +32,13 @@ export const RunCommandInspector = memo<BuiltinInspectorProps<RunCommandParams,
|
||||
if (isArgumentsStreaming) {
|
||||
if (!description)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.runCommand')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.runCommand')}: </span>
|
||||
<span className={highlightTextStyles.primary}>{description}</span>
|
||||
</div>
|
||||
@@ -58,7 +50,7 @@ export const RunCommandInspector = memo<BuiltinInspectorProps<RunCommandParams,
|
||||
const isSuccess = result?.success && result?.exit_code === 0;
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span style={{ marginInlineStart: 2 }}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.runCommand')}: </span>
|
||||
{description && <span className={highlightTextStyles.primary}>{description}</span>}
|
||||
|
||||
@@ -3,25 +3,14 @@
|
||||
import { type LocalSearchFilesParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { cssVar, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { type LocalFileSearchState } from '../../..';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const SearchLocalFilesInspector = memo<
|
||||
BuiltinInspectorProps<LocalSearchFilesParams, LocalFileSearchState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, pluginState, isLoading }) => {
|
||||
@@ -33,13 +22,13 @@ export const SearchLocalFilesInspector = memo<
|
||||
if (isArgumentsStreaming) {
|
||||
if (!keywords)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.searchLocalFiles')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.searchLocalFiles')}: </span>
|
||||
<span className={highlightTextStyles.primary}>{keywords}</span>
|
||||
</div>
|
||||
@@ -51,7 +40,7 @@ export const SearchLocalFilesInspector = memo<
|
||||
const hasResults = resultCount > 0;
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
||||
<span style={{ marginInlineStart: 2 }}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.searchLocalFiles')}: </span>
|
||||
{keywords && <span className={highlightTextStyles.primary}>{keywords}</span>}
|
||||
|
||||
@@ -3,26 +3,15 @@
|
||||
import { type WriteLocalFileParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon, Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { cssVar, cx } from 'antd-style';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import { FilePathDisplay } from '../../components/FilePathDisplay';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const WriteLocalFileInspector = memo<BuiltinInspectorProps<WriteLocalFileParams>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
@@ -36,14 +25,16 @@ export const WriteLocalFileInspector = memo<BuiltinInspectorProps<WriteLocalFile
|
||||
// During argument streaming without path
|
||||
if (isArgumentsStreaming && !filePath) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.writeLocalFile')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div
|
||||
className={cx(inspectorTextStyles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
>
|
||||
<span>{t('builtins.lobe-local-system.apiName.writeLocalFile')}: </span>
|
||||
<FilePathDisplay filePath={filePath} />
|
||||
{lines > 0 && (
|
||||
|
||||
@@ -1,25 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { CreateDocumentArgs, CreateDocumentState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const CreateDocumentInspector = memo<
|
||||
BuiltinInspectorProps<CreateDocumentArgs, CreateDocumentState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming, isLoading }) => {
|
||||
@@ -30,7 +19,7 @@ export const CreateDocumentInspector = memo<
|
||||
// During streaming without title, show init
|
||||
if (isArgumentsStreaming && !title) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-notebook.apiName.createDocument')}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -38,7 +27,10 @@ export const CreateDocumentInspector = memo<
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(styles.root, (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText)}
|
||||
className={cx(
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-notebook.apiName.createDocument')}: </span>
|
||||
{title && <span className={highlightTextStyles.primary}>{title}</span>}
|
||||
|
||||
@@ -2,25 +2,14 @@
|
||||
|
||||
import type { EditTitleArgs } from '@lobechat/editor-runtime';
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { EditTitleState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const EditTitleInspector = memo<BuiltinInspectorProps<EditTitleArgs, EditTitleState>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
@@ -28,7 +17,9 @@ export const EditTitleInspector = memo<BuiltinInspectorProps<EditTitleArgs, Edit
|
||||
const title = args?.title || partialArgs?.title;
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div
|
||||
className={cx(inspectorTextStyles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
>
|
||||
{title ? (
|
||||
<Trans
|
||||
components={{ title: <span className={highlightTextStyles.gold} /> }}
|
||||
|
||||
@@ -5,25 +5,24 @@ import { createStaticStyles, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { oneLineEllipsis, shinyTextStyles } from '@/styles';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
done: css`
|
||||
color: ${cssVar.colorTextDescription};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const GetPageContentInspector = memo<BuiltinInspectorProps>(({ isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming ? shinyTextStyles.shinyText : styles.done)}>
|
||||
<div
|
||||
className={cx(
|
||||
oneLineEllipsis,
|
||||
isArgumentsStreaming ? shinyTextStyles.shinyText : styles.done,
|
||||
)}
|
||||
>
|
||||
<span>{t('builtins.lobe-page-agent.apiName.getPageContent')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,18 +8,12 @@ import { Plus } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { oneLineEllipsis, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { InitDocumentState } from '../../../types';
|
||||
import { AnimatedNumber } from '../../components/AnimatedNumber';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
`,
|
||||
title: css`
|
||||
margin-inline-end: 8px;
|
||||
color: ${cssVar.colorText};
|
||||
@@ -43,14 +37,14 @@ export const InitPageInspector = memo<BuiltinInspectorProps<InitDocumentArgs, In
|
||||
if (isArgumentsStreaming) {
|
||||
if (!hasContent)
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-page-agent.apiName.initPage')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
// During streaming with content, show "creating" title with shiny effect
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={oneLineEllipsis}>
|
||||
<span className={shinyTextStyles.shinyText}>
|
||||
{t('builtins.lobe-page-agent.apiName.initPage.creating')}
|
||||
</span>
|
||||
@@ -74,7 +68,7 @@ export const InitPageInspector = memo<BuiltinInspectorProps<InitDocumentArgs, In
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={oneLineEllipsis}>
|
||||
<span className={styles.title}>
|
||||
{t('builtins.lobe-page-agent.apiName.initPage.result')}
|
||||
</span>
|
||||
|
||||
@@ -8,17 +8,11 @@ import { DiffIcon, Minus, Plus } from 'lucide-react';
|
||||
import { type ReactNode, memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
import { oneLineEllipsis, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { ModifyNodesState } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
`,
|
||||
separator: css`
|
||||
margin-inline: 2px;
|
||||
color: ${cssVar.colorTextQuaternary};
|
||||
@@ -66,7 +60,7 @@ export const ModifyNodesInspector = memo<BuiltinInspectorProps<ModifyNodesArgs,
|
||||
// During streaming without operations yet, show init message
|
||||
if (isArgumentsStreaming && !hasOperations) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-page-agent.apiName.modifyNodes.init')}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -100,7 +94,7 @@ export const ModifyNodesInspector = memo<BuiltinInspectorProps<ModifyNodesArgs,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div className={cx(oneLineEllipsis, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<span className={styles.title}>{t('builtins.lobe-page-agent.apiName.modifyNodes')}</span>
|
||||
{statsParts.length > 0 && (
|
||||
<>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ArrowRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { ReplaceTextState } from '../../../types';
|
||||
|
||||
@@ -21,14 +21,6 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
text-decoration: line-through;
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
title: css`
|
||||
margin-inline-end: 8px;
|
||||
color: ${cssVar.colorText};
|
||||
@@ -45,7 +37,7 @@ export const ReplaceTextInspector = memo<BuiltinInspectorProps<ReplaceTextArgs,
|
||||
// During streaming without searchText yet, show init message
|
||||
if (isArgumentsStreaming && !from) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-page-agent.apiName.replaceText.init')}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -55,7 +47,9 @@ export const ReplaceTextInspector = memo<BuiltinInspectorProps<ReplaceTextArgs,
|
||||
const hasResult = from && to !== undefined;
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div
|
||||
className={cx(inspectorTextStyles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
>
|
||||
<span className={styles.title}>{t('builtins.lobe-page-agent.apiName.replaceText')}</span>
|
||||
{hasResult && (
|
||||
<>
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
interface CrawlMultiPagesParams {
|
||||
urls: string[];
|
||||
@@ -42,14 +31,16 @@ export const CrawlMultiPagesInspector = memo<BuiltinInspectorProps<CrawlMultiPag
|
||||
|
||||
if (isArgumentsStreaming && !displayText) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.crawlMultiPages')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div
|
||||
className={cx(inspectorTextStyles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
>
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.crawlMultiPages')}: </span>
|
||||
{displayText && <span className={highlightTextStyles.gold}>{displayText}</span>}
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
interface CrawlSinglePageParams {
|
||||
url: string;
|
||||
@@ -30,14 +19,16 @@ export const CrawlSinglePageInspector = memo<BuiltinInspectorProps<CrawlSinglePa
|
||||
|
||||
if (isArgumentsStreaming && !url) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.crawlSinglePage')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}>
|
||||
<div
|
||||
className={cx(inspectorTextStyles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
>
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.crawlSinglePage')}: </span>
|
||||
{url && <span className={highlightTextStyles.gold}>{url}</span>}
|
||||
</div>
|
||||
|
||||
@@ -6,22 +6,11 @@ import {
|
||||
type UniformSearchResponse,
|
||||
} from '@lobechat/types';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { cssVar, cx } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { highlightTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
|
||||
|
||||
export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery, UniformSearchResponse>>(
|
||||
({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
|
||||
@@ -33,7 +22,7 @@ export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery, UniformSe
|
||||
|
||||
if (isArgumentsStreaming && !query) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.search')}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -42,7 +31,7 @@ export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery, UniformSe
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
styles.root,
|
||||
inspectorTextStyles.root,
|
||||
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -115,7 +115,7 @@ export class ChatGroupModel {
|
||||
async update(id: string, value: Partial<ChatGroupItem>): Promise<ChatGroupItem> {
|
||||
const [result] = await this.db
|
||||
.update(chatGroups)
|
||||
.set({ ...value, updatedAt: new Date() })
|
||||
.set(value)
|
||||
.where(and(eq(chatGroups.id, id), eq(chatGroups.userId, this.userId)))
|
||||
.returning();
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { TaskDetail, UIChatMessage } from '../message';
|
||||
import { ChatTopic } from '../topic';
|
||||
|
||||
export interface LobeChatGroupMetaConfig {
|
||||
avatar?: string;
|
||||
backgroundColor?: string;
|
||||
description: string;
|
||||
title: string;
|
||||
}
|
||||
@@ -75,6 +77,8 @@ export interface NewChatGroupAgent {
|
||||
|
||||
// New Chat Group type for creating groups (independent from schema)
|
||||
export interface NewChatGroup {
|
||||
avatar?: string | null;
|
||||
backgroundColor?: string | null;
|
||||
clientId?: string | null;
|
||||
config?: LobeChatGroupConfig | null;
|
||||
description?: string | null;
|
||||
@@ -88,10 +92,14 @@ export interface NewChatGroup {
|
||||
// Chat Group Item type (independent from schema)
|
||||
export interface ChatGroupItem {
|
||||
accessedAt?: Date;
|
||||
avatar?: string | null;
|
||||
backgroundColor?: string | null;
|
||||
clientId?: string | null;
|
||||
config?: LobeChatGroupConfig | null;
|
||||
content?: string | null;
|
||||
createdAt: Date;
|
||||
description?: string | null;
|
||||
editorData?: Record<string, any> | null;
|
||||
groupId?: string | null;
|
||||
id: string;
|
||||
pinned?: boolean | null;
|
||||
|
||||
@@ -55,7 +55,7 @@ const Nav = memo(() => {
|
||||
active={isProfileActive}
|
||||
icon={BotPromptIcon}
|
||||
onClick={() => {
|
||||
switchTopic(undefined, true);
|
||||
switchTopic(null, { skipRefreshMessage: true });
|
||||
router.push(urlJoin('/agent', agentId!, 'profile'));
|
||||
}}
|
||||
title={t('tab.profile')}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ReactMathPlugin,
|
||||
ReactTablePlugin,
|
||||
} from '@lobehub/editor';
|
||||
import { Editor, useEditor } from '@lobehub/editor/react';
|
||||
import { Editor, useEditor, useEditorState } from '@lobehub/editor/react';
|
||||
import { ActionIcon, Flexbox, Icon, Input, Tag, Text } from '@lobehub/ui';
|
||||
import { useDebounceFn } from 'ahooks';
|
||||
import { App, Card, Checkbox, Empty, InputNumber, Select, Switch, TimePicker, message } from 'antd';
|
||||
@@ -33,7 +33,7 @@ import useSWR from 'swr';
|
||||
import AutoSaveHint from '@/components/Editor/AutoSaveHint';
|
||||
import Loading from '@/components/Loading/BrandTextLoading';
|
||||
import type { ExecutionConditions, UpdateAgentCronJobData } from '@/database/schemas/agentCronJob';
|
||||
import TypoBar from '@/features/EditorModal/Typobar';
|
||||
import { InlineToolbar } from '@/features/EditorCanvas';
|
||||
import NavHeader from '@/features/NavHeader';
|
||||
import WideScreenContainer from '@/features/WideScreenContainer';
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
@@ -142,6 +142,7 @@ const CronJobDetailPage = memo(() => {
|
||||
const router = useQueryRoute();
|
||||
const { modal } = App.useApp();
|
||||
const editor = useEditor();
|
||||
const editorState = useEditorState(editor);
|
||||
const enableRichRender = useUserStore(labPreferSelectors.enableInputMarkdown);
|
||||
const [editorReady, setEditorReady] = useState(false);
|
||||
|
||||
@@ -628,7 +629,7 @@ const CronJobDetailPage = memo(() => {
|
||||
style={{ borderRadius: 12, overflow: 'hidden' }}
|
||||
styles={{ body: { padding: 0 } }}
|
||||
>
|
||||
{enableRichRender && <TypoBar editor={editor} />}
|
||||
{enableRichRender && <InlineToolbar editor={editor} editorState={editorState} />}
|
||||
<Flexbox padding={16} style={{ minHeight: 220 }}>
|
||||
<Editor
|
||||
content={''}
|
||||
|
||||
@@ -88,7 +88,7 @@ const ProfileEditor = memo(() => {
|
||||
onClick={() => {
|
||||
if (!agentId) return;
|
||||
// Clear topicId before navigating to prevent stale state
|
||||
switchTopic(undefined, true);
|
||||
switchTopic(null, { skipRefreshMessage: true });
|
||||
router.push(urlJoin('/agent', agentId));
|
||||
}}
|
||||
type={'primary'}
|
||||
|
||||
@@ -28,26 +28,25 @@ export interface Action {
|
||||
|
||||
export type Store = State & Action;
|
||||
|
||||
// Create debounced save function outside of store for reuse
|
||||
const createDebouncedSave = (
|
||||
get: () => Store,
|
||||
updateConfig: (payload: SaveConfigPayload) => Promise<void>,
|
||||
) =>
|
||||
debounce(
|
||||
async (payload: SaveConfigPayload) => {
|
||||
try {
|
||||
await updateConfig(payload);
|
||||
} catch (error) {
|
||||
console.error('[ProfileEditor] Failed to save:', error);
|
||||
}
|
||||
},
|
||||
EDITOR_DEBOUNCE_TIME,
|
||||
{ leading: false, maxWait: EDITOR_MAX_WAIT, trailing: true },
|
||||
);
|
||||
// Store the latest updateConfig reference to avoid stale closures
|
||||
let updateConfigRef: ((payload: SaveConfigPayload) => Promise<void>) | null = null;
|
||||
|
||||
export const store: (initState?: Partial<State>) => StateCreator<Store> =
|
||||
(initState) => (set, get) => {
|
||||
let debouncedSave: ReturnType<typeof createDebouncedSave> | null = null;
|
||||
// Create debounced save that uses the latest callback reference
|
||||
const debouncedSave = debounce(
|
||||
async (payload: SaveConfigPayload) => {
|
||||
try {
|
||||
if (updateConfigRef) {
|
||||
await updateConfigRef(payload);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ProfileEditor] Failed to save:', error);
|
||||
}
|
||||
},
|
||||
EDITOR_DEBOUNCE_TIME,
|
||||
{ leading: false, maxWait: EDITOR_MAX_WAIT, trailing: true },
|
||||
);
|
||||
|
||||
return {
|
||||
...initialState,
|
||||
@@ -115,10 +114,8 @@ export const store: (initState?: Partial<State>) => StateCreator<Store> =
|
||||
const { editor } = get();
|
||||
if (!editor) return;
|
||||
|
||||
// Create debounced save on first use
|
||||
if (!debouncedSave) {
|
||||
debouncedSave = createDebouncedSave(get, updateConfig);
|
||||
}
|
||||
// Always update ref to use the latest callback
|
||||
updateConfigRef = updateConfig;
|
||||
|
||||
try {
|
||||
const markdownContent = (editor.getDocument('markdown') as unknown as string) || '';
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useUnmount } from 'ahooks';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { createStoreUpdater } from 'zustand-utils';
|
||||
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
|
||||
@@ -9,14 +10,18 @@ const GroupIdSync = () => {
|
||||
const useAgentGroupStoreUpdater = createStoreUpdater(useAgentGroupStore);
|
||||
const useChatStoreUpdater = createStoreUpdater(useChatStore);
|
||||
const params = useParams<{ gid?: string }>();
|
||||
const router = useQueryRoute();
|
||||
|
||||
// Sync groupId to agentGroupStore and chatStore
|
||||
useAgentGroupStoreUpdater('activeGroupId', params.gid);
|
||||
useChatStoreUpdater('activeGroupId', params.gid);
|
||||
|
||||
// Inject router to agentGroupStore for navigation
|
||||
useAgentGroupStoreUpdater('router', router);
|
||||
|
||||
// Clear activeGroupId when unmounting (leaving group page)
|
||||
useUnmount(() => {
|
||||
useAgentGroupStore.setState({ activeGroupId: undefined });
|
||||
useAgentGroupStore.setState({ activeGroupId: undefined, router: undefined });
|
||||
useChatStore.setState({ activeGroupId: undefined, activeTopicId: undefined });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import type { AgentItem } from '@lobechat/types';
|
||||
import { Avatar, Center, Flexbox, Popover, Text, Tooltip } from '@lobehub/ui';
|
||||
import { ActionIcon, Avatar, Center, Flexbox, Popover, Text, Tooltip } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar } from 'antd-style';
|
||||
import { Settings } from 'lucide-react';
|
||||
import { type PropsWithChildren, memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@/const/meta';
|
||||
import ModelSelect from '@/features/ModelSelect';
|
||||
@@ -73,6 +75,7 @@ interface AgentProfilePopupProps extends PropsWithChildren {
|
||||
|
||||
const AgentProfilePopup = memo<AgentProfilePopupProps>(({ agent, groupId, children }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const navigate = useNavigate();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -90,10 +93,10 @@ const AgentProfilePopup = memo<AgentProfilePopupProps>(({ agent, groupId, childr
|
||||
}
|
||||
};
|
||||
|
||||
// const handleChat = () => {
|
||||
// setOpen(false);
|
||||
// onChat();
|
||||
// };
|
||||
const handleSettings = () => {
|
||||
setOpen(false);
|
||||
navigate(`/group/${groupId}/profile?tab=${agent.id}`);
|
||||
};
|
||||
|
||||
const content = (
|
||||
<Flexbox className={styles.container}>
|
||||
@@ -127,9 +130,26 @@ const AgentProfilePopup = memo<AgentProfilePopupProps>(({ agent, groupId, childr
|
||||
}}
|
||||
/>
|
||||
<Flexbox gap={2}>
|
||||
<Text className={styles.name} ellipsis>
|
||||
{agent.title || t('defaultSession', { ns: 'common' })}
|
||||
</Text>
|
||||
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
||||
<Text className={styles.name} ellipsis>
|
||||
{agent.title || t('defaultSession', { ns: 'common' })}
|
||||
</Text>
|
||||
|
||||
{/* Settings Button */}
|
||||
<Flexbox
|
||||
align="center"
|
||||
horizontal
|
||||
justify="flex-end"
|
||||
style={{ paddingBlockStart: 0 }}
|
||||
>
|
||||
<ActionIcon
|
||||
icon={Settings}
|
||||
onClick={handleSettings}
|
||||
size="small"
|
||||
title={t('groupSidebar.agentProfile.settings')}
|
||||
/>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
{agent.description && (
|
||||
<Tooltip title={agent.description}>
|
||||
<Text className={styles.description} ellipsis={{ rows: 2 }}>
|
||||
@@ -149,18 +169,6 @@ const AgentProfilePopup = memo<AgentProfilePopupProps>(({ agent, groupId, childr
|
||||
value={{ model: agent.model!, provider: agent.provider! }}
|
||||
/>
|
||||
</Flexbox>
|
||||
|
||||
{/* Actions */}
|
||||
{/*<Flexbox className={styles.section} style={{ paddingBlockStart: 0 }}>*/}
|
||||
{/* <Button*/}
|
||||
{/* className={styles.chatButton}*/}
|
||||
{/* icon={<MessageSquare size={14} />}*/}
|
||||
{/* onClick={handleChat}*/}
|
||||
{/* type="primary"*/}
|
||||
{/* >*/}
|
||||
{/* {t('groupSidebar.agentProfile.chat')}*/}
|
||||
{/* </Button>*/}
|
||||
{/*</Flexbox>*/}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -171,7 +179,7 @@ const AgentProfilePopup = memo<AgentProfilePopupProps>(({ agent, groupId, childr
|
||||
open={open}
|
||||
placement="right"
|
||||
styles={{
|
||||
content: { overflow: 'hidden', padding: 0 },
|
||||
content: { borderRadius: 12, overflow: 'hidden', padding: 0 },
|
||||
}}
|
||||
trigger="click"
|
||||
>
|
||||
|
||||
@@ -109,6 +109,7 @@ const GroupMember = memo<GroupMemberProps>(({ addModalOpen, onAddModalOpenChange
|
||||
}
|
||||
avatar={item.avatar || DEFAULT_AVATAR}
|
||||
background={item.backgroundColor ?? undefined}
|
||||
isExternal={!item.virtual}
|
||||
title={item.title || t('defaultSession', { ns: 'common' })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Avatar } from '@lobehub/ui';
|
||||
import { Avatar, Flexbox, Tag } from '@lobehub/ui';
|
||||
import { type ReactNode, memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@/const/meta';
|
||||
import NavItem from '@/features/NavPanel/components/NavItem';
|
||||
@@ -10,26 +11,42 @@ interface GroupMemberItemProps {
|
||||
actions?: ReactNode;
|
||||
avatar?: string;
|
||||
background?: string;
|
||||
isExternal?: boolean;
|
||||
onClick?: () => void;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const GroupMemberItem = memo<GroupMemberItemProps>(({ title, avatar, background, actions }) => {
|
||||
return (
|
||||
<NavItem
|
||||
actions={actions}
|
||||
icon={
|
||||
<Avatar
|
||||
avatar={avatar || DEFAULT_AVATAR}
|
||||
background={background}
|
||||
emojiScaleWithBackground
|
||||
size={24}
|
||||
style={{ flex: 'none' }}
|
||||
/>
|
||||
}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const GroupMemberItem = memo<GroupMemberItemProps>(
|
||||
({ title, avatar, background, actions, isExternal }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
|
||||
return (
|
||||
<NavItem
|
||||
actions={actions}
|
||||
icon={
|
||||
<Avatar
|
||||
avatar={avatar || DEFAULT_AVATAR}
|
||||
background={background}
|
||||
emojiScaleWithBackground
|
||||
size={24}
|
||||
style={{ flex: 'none' }}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<Flexbox align="center" gap={4} horizontal>
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{title}
|
||||
</span>
|
||||
{isExternal && (
|
||||
<Tag size="small" style={{ flexShrink: 0 }}>
|
||||
{t('group.profile.external')}
|
||||
</Tag>
|
||||
)}
|
||||
</Flexbox>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default GroupMemberItem;
|
||||
|
||||
@@ -4,12 +4,9 @@ import { ActionIcon } from '@lobehub/ui';
|
||||
import { MessageSquarePlusIcon } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { settingsSelectors } from '@/store/user/selectors';
|
||||
import { HotkeyEnum } from '@/types/hotkey';
|
||||
@@ -17,17 +14,12 @@ import { HotkeyEnum } from '@/types/hotkey';
|
||||
const AddTopicButon = memo(() => {
|
||||
const { t } = useTranslation('topic');
|
||||
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.SaveTopic));
|
||||
const activeGroupId = useAgentGroupStore((s) => s.activeGroupId);
|
||||
const router = useQueryRoute();
|
||||
const switchToNewTopic = useAgentGroupStore((s) => s.switchToNewTopic);
|
||||
|
||||
return (
|
||||
<ActionIcon
|
||||
icon={MessageSquarePlusIcon}
|
||||
onClick={() => {
|
||||
if (!activeGroupId) return;
|
||||
useChatStore.setState({ activeTopicId: undefined });
|
||||
router.push(urlJoin('/group', activeGroupId), { query: { thread: null, topic: null } });
|
||||
}}
|
||||
onClick={switchToNewTopic}
|
||||
size={DESKTOP_HEADER_ICON_SIZE}
|
||||
title={t('actions.addNewTopic')}
|
||||
tooltipProps={{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { BotPromptIcon } from '@lobehub/ui/icons';
|
||||
import { SearchIcon } from 'lucide-react';
|
||||
import { MessageSquarePlusIcon, SearchIcon } from 'lucide-react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -11,12 +11,14 @@ import urlJoin from 'url-join';
|
||||
|
||||
import NavItem from '@/features/NavPanel/components/NavItem';
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
||||
|
||||
const Nav = memo(() => {
|
||||
const { t } = useTranslation('chat');
|
||||
const { t: tTopic } = useTranslation('topic');
|
||||
const params = useParams();
|
||||
const groupId = params.gid;
|
||||
const pathname = usePathname();
|
||||
@@ -25,15 +27,21 @@ const Nav = memo(() => {
|
||||
const { isAgentEditable } = useServerConfigStore(featureFlagsSelectors);
|
||||
const toggleCommandMenu = useGlobalStore((s) => s.toggleCommandMenu);
|
||||
const switchTopic = useChatStore((s) => s.switchTopic);
|
||||
const switchToNewTopic = useAgentGroupStore((s) => s.switchToNewTopic);
|
||||
|
||||
return (
|
||||
<Flexbox gap={1} paddingInline={4}>
|
||||
<NavItem
|
||||
icon={MessageSquarePlusIcon}
|
||||
onClick={switchToNewTopic}
|
||||
title={tTopic('actions.addNewTopic')}
|
||||
/>
|
||||
{isAgentEditable && (
|
||||
<NavItem
|
||||
active={isProfileActive}
|
||||
icon={BotPromptIcon}
|
||||
onClick={() => {
|
||||
switchTopic(undefined, true);
|
||||
switchTopic(null, { skipRefreshMessage: true });
|
||||
router.push(urlJoin('/group', groupId!, 'profile'));
|
||||
}}
|
||||
title={t('tab.groupProfile')}
|
||||
|
||||
@@ -4,14 +4,13 @@ import { type PropsWithChildren, memo } from 'react';
|
||||
|
||||
import SideBarHeaderLayout from '@/features/NavPanel/SideBarHeaderLayout';
|
||||
|
||||
import AddTopicButon from './AddTopicButon';
|
||||
import Agent from './Agent';
|
||||
import Nav from './Nav';
|
||||
|
||||
const HeaderInfo = memo<PropsWithChildren>(() => {
|
||||
return (
|
||||
<>
|
||||
<SideBarHeaderLayout left={<Agent />} right={<AddTopicButon />} />
|
||||
<SideBarHeaderLayout left={<Agent />} />
|
||||
<Nav />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ interface AgentBuilderProviderProps {
|
||||
* Uses 'group_agent_builder' scope with groupId to isolate messages per group
|
||||
*/
|
||||
const AgentBuilderProvider = memo<AgentBuilderProviderProps>(({ agentId, children }) => {
|
||||
// Use activeTopicId from chatStore (synced with URL query 'bt' via ProfileHydration)
|
||||
const activeTopicId = useChatStore((s) => s.activeTopicId);
|
||||
|
||||
// Build conversation context for group agent builder
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ActionIcon, DropdownMenu, type DropdownMenuCheckboxItem, Tag } from '@lobehub/ui';
|
||||
import { Clock3Icon, PlusIcon } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
||||
@@ -18,11 +18,17 @@ const TopicSelector = memo<TopicSelectorProps>(({ agentId }) => {
|
||||
// Fetch topics for the group agent builder
|
||||
useChatStore((s) => s.useFetchTopics)(true, { agentId });
|
||||
|
||||
const [activeTopicId, switchTopic, topics] = useChatStore((s) => [
|
||||
s.activeTopicId,
|
||||
s.switchTopic,
|
||||
topicSelectors.getTopicsByAgentId(agentId)(s),
|
||||
]);
|
||||
// Use activeTopicId from chatStore (synced with URL query 'bt' via ProfileHydration)
|
||||
const [activeTopicId, switchTopic] = useChatStore((s) => [s.activeTopicId, s.switchTopic]);
|
||||
const topics = useChatStore((s) => topicSelectors.getTopicsByAgentId(agentId)(s));
|
||||
|
||||
// Switch topic - ProfileHydration handles URL sync automatically
|
||||
const handleSwitchTopic = useCallback(
|
||||
(topicId?: string) => {
|
||||
switchTopic(topicId);
|
||||
},
|
||||
[switchTopic],
|
||||
);
|
||||
|
||||
// Find active topic from the agent's topics list directly
|
||||
const activeTopic = useMemo(
|
||||
@@ -39,12 +45,12 @@ const TopicSelector = memo<TopicSelectorProps>(({ agentId }) => {
|
||||
label: topic.title,
|
||||
onCheckedChange: (checked) => {
|
||||
if (checked) {
|
||||
switchTopic(topic.id);
|
||||
handleSwitchTopic(topic.id);
|
||||
}
|
||||
},
|
||||
type: 'checkbox',
|
||||
})),
|
||||
[topics, switchTopic, activeTopicId],
|
||||
[topics, handleSwitchTopic, activeTopicId],
|
||||
);
|
||||
const isEmpty = !topics || topics.length === 0;
|
||||
|
||||
@@ -55,7 +61,7 @@ const TopicSelector = memo<TopicSelectorProps>(({ agentId }) => {
|
||||
<>
|
||||
<ActionIcon
|
||||
icon={PlusIcon}
|
||||
onClick={() => switchTopic()}
|
||||
onClick={() => handleSwitchTopic(undefined)}
|
||||
size={DESKTOP_HEADER_ICON_SIZE}
|
||||
title={t('actions.addNewTopic')}
|
||||
/>
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import { BUILTIN_AGENT_SLUGS } from '@lobechat/builtin-agents';
|
||||
import { DraggablePanel } from '@lobehub/ui';
|
||||
import { cssVar } from 'antd-style';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import Loading from '@/components/Loading/BrandTextLoading';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { builtinAgentSelectors } from '@/store/agent/selectors';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { systemStatusSelectors } from '@/store/global/selectors';
|
||||
import { useGroupProfileStore } from '@/store/groupProfile';
|
||||
|
||||
import { useProfileStore } from '../store';
|
||||
import AgentBuilderConversation from './AgentBuilderConversation';
|
||||
import AgentBuilderProvider from './AgentBuilderProvider';
|
||||
|
||||
const AgentBuilder = memo(() => {
|
||||
const chatPanelExpanded = useProfileStore((s) => s.chatPanelExpanded);
|
||||
const setChatPanelExpanded = useProfileStore((s) => s.setChatPanelExpanded);
|
||||
const chatPanelExpanded = useGroupProfileStore((s) => s.chatPanelExpanded);
|
||||
const setChatPanelExpanded = useGroupProfileStore((s) => s.setChatPanelExpanded);
|
||||
const groupAgentBuilderId = useAgentStore(builtinAgentSelectors.groupAgentBuilderId);
|
||||
|
||||
const [width, setWidth] = useState<string | number>(360);
|
||||
const [width, updateSystemStatus] = useGlobalStore((s) => [
|
||||
systemStatusSelectors.groupAgentBuilderPanelWidth(s),
|
||||
s.updateSystemStatus,
|
||||
]);
|
||||
|
||||
const useInitBuiltinAgent = useAgentStore((s) => s.useInitBuiltinAgent);
|
||||
useInitBuiltinAgent(BUILTIN_AGENT_SLUGS.groupAgentBuilder);
|
||||
@@ -31,7 +36,8 @@ const AgentBuilder = memo(() => {
|
||||
onExpandChange={setChatPanelExpanded}
|
||||
onSizeChange={(_, size) => {
|
||||
if (size?.width) {
|
||||
setWidth(size.width);
|
||||
const w = typeof size.width === 'string' ? Number.parseInt(size.width) : size.width;
|
||||
if (!!w) updateSystemStatus({ groupAgentBuilderPanelWidth: w });
|
||||
}
|
||||
}}
|
||||
placement="right"
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import { HotkeyEnum, getHotkeyById } from '@lobehub/editor';
|
||||
import { FloatActions } from '@lobehub/editor/react';
|
||||
import { type ChatInputActionsProps } from '@lobehub/editor/react';
|
||||
import {
|
||||
BoldIcon,
|
||||
CodeXmlIcon,
|
||||
ItalicIcon,
|
||||
ListIcon,
|
||||
ListOrderedIcon,
|
||||
ListTodoIcon,
|
||||
MessageSquareQuote,
|
||||
SigmaIcon,
|
||||
SquareDashedBottomCodeIcon,
|
||||
StrikethroughIcon,
|
||||
UnderlineIcon,
|
||||
} from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useProfileStore } from '../store';
|
||||
|
||||
const TypoBar = memo(() => {
|
||||
const { t } = useTranslation('editor');
|
||||
const editorState = useProfileStore((s) => s.editorState);
|
||||
|
||||
const items: ChatInputActionsProps['items'] = useMemo(() => {
|
||||
if (!editorState) return [];
|
||||
|
||||
return [
|
||||
{
|
||||
active: editorState.isBold,
|
||||
icon: BoldIcon,
|
||||
key: 'bold',
|
||||
label: t('typobar.bold'),
|
||||
onClick: editorState.bold,
|
||||
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Bold).keys },
|
||||
},
|
||||
{
|
||||
active: editorState.isItalic,
|
||||
icon: ItalicIcon,
|
||||
key: 'italic',
|
||||
label: t('typobar.italic'),
|
||||
onClick: editorState.italic,
|
||||
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Italic).keys },
|
||||
},
|
||||
{
|
||||
active: editorState.isUnderline,
|
||||
icon: UnderlineIcon,
|
||||
key: 'underline',
|
||||
label: t('typobar.underline'),
|
||||
onClick: editorState.underline,
|
||||
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Underline).keys },
|
||||
},
|
||||
{
|
||||
active: editorState.isStrikethrough,
|
||||
icon: StrikethroughIcon,
|
||||
key: 'strikethrough',
|
||||
label: t('typobar.strikethrough'),
|
||||
onClick: editorState.strikethrough,
|
||||
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Strikethrough).keys },
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
|
||||
{
|
||||
icon: ListIcon,
|
||||
key: 'bulletList',
|
||||
label: t('typobar.bulletList'),
|
||||
onClick: editorState.bulletList,
|
||||
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.BulletList).keys },
|
||||
},
|
||||
{
|
||||
icon: ListOrderedIcon,
|
||||
key: 'numberlist',
|
||||
label: t('typobar.numberList'),
|
||||
onClick: editorState.numberList,
|
||||
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.NumberList).keys },
|
||||
},
|
||||
{
|
||||
icon: ListTodoIcon,
|
||||
key: 'tasklist',
|
||||
label: t('typobar.taskList'),
|
||||
onClick: editorState.checkList,
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
active: editorState.isBlockquote,
|
||||
icon: MessageSquareQuote,
|
||||
key: 'blockquote',
|
||||
label: t('typobar.blockquote'),
|
||||
onClick: editorState.blockquote,
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
icon: SigmaIcon,
|
||||
key: 'math',
|
||||
label: t('typobar.tex'),
|
||||
onClick: editorState.insertMath,
|
||||
},
|
||||
{
|
||||
active: editorState.isCode,
|
||||
icon: CodeXmlIcon,
|
||||
key: 'code',
|
||||
label: t('typobar.code'),
|
||||
onClick: editorState.code,
|
||||
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.CodeInline).keys },
|
||||
},
|
||||
{
|
||||
icon: SquareDashedBottomCodeIcon,
|
||||
key: 'codeblock',
|
||||
label: t('typobar.codeblock'),
|
||||
onClick: editorState.codeblock,
|
||||
},
|
||||
].filter(Boolean) as ChatInputActionsProps['items'];
|
||||
}, [editorState, t]);
|
||||
|
||||
if (!editorState) return null;
|
||||
|
||||
return <FloatActions items={items} />;
|
||||
});
|
||||
|
||||
TypoBar.displayName = 'TypoBar';
|
||||
|
||||
export default TypoBar;
|
||||
@@ -1,138 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
ReactCodePlugin,
|
||||
ReactCodemirrorPlugin,
|
||||
ReactHRPlugin,
|
||||
ReactLinkHighlightPlugin,
|
||||
ReactListPlugin,
|
||||
ReactMathPlugin,
|
||||
ReactMentionPlugin,
|
||||
ReactTablePlugin,
|
||||
ReactToolbarPlugin,
|
||||
} from '@lobehub/editor';
|
||||
import { Editor } from '@lobehub/editor/react';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
||||
|
||||
import { useMentionOptions } from '../ProfileEditor/MentionList';
|
||||
import { EMPTY_EDITOR_STATE } from '../constants';
|
||||
import { useProfileStore } from '../store';
|
||||
import TypoBar from './TypoBar';
|
||||
import { useSlashItems } from './useSlashItems';
|
||||
|
||||
const EditorCanvas = memo(() => {
|
||||
const { t } = useTranslation('setting');
|
||||
const [editorInit, setEditorInit] = useState(false);
|
||||
const [contentInit, setContentInit] = useState(false);
|
||||
|
||||
// Get systemPrompt from agentGroup store (groupMap.config.systemPrompt)
|
||||
const groupConfig = useAgentGroupStore(agentGroupSelectors.currentGroupConfig, isEqual);
|
||||
const systemRole = groupConfig?.systemPrompt;
|
||||
const updateGroupConfig = useAgentGroupStore((s) => s.updateGroupConfig);
|
||||
|
||||
// Get streaming state from agentGroup store
|
||||
const streamingInProgress = useAgentGroupStore((s) => s.streamingSystemPromptInProgress);
|
||||
const streamingSystemRole = useAgentGroupStore((s) => s.streamingSystemPrompt);
|
||||
|
||||
// For group profile, we don't use editorData - just systemPrompt
|
||||
const editorData = undefined;
|
||||
const [initialLoad] = useState(EMPTY_EDITOR_STATE);
|
||||
const mentionOptions = useMentionOptions();
|
||||
const editor = useProfileStore((s) => s.editor);
|
||||
const handleContentChange = useProfileStore((s) => s.handleContentChange);
|
||||
const slashItems = useSlashItems();
|
||||
|
||||
const prevStreamingRef = useRef<string | undefined>(undefined);
|
||||
|
||||
// Wrap handleContentChange with updateGroupConfig
|
||||
const handleChange = useCallback(() => {
|
||||
// Don't trigger save during streaming
|
||||
if (streamingInProgress) return;
|
||||
handleContentChange(async (config) => {
|
||||
// Only update systemPrompt in group config
|
||||
if (config.systemRole !== undefined) {
|
||||
await updateGroupConfig({ systemPrompt: config.systemRole });
|
||||
}
|
||||
});
|
||||
}, [handleContentChange, updateGroupConfig, streamingInProgress]);
|
||||
|
||||
// Handle streaming updates - update editor with streaming content
|
||||
useEffect(() => {
|
||||
if (!editor || !editorInit) return;
|
||||
if (!streamingInProgress) {
|
||||
prevStreamingRef.current = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only update if content has changed
|
||||
if (streamingSystemRole !== prevStreamingRef.current) {
|
||||
prevStreamingRef.current = streamingSystemRole;
|
||||
try {
|
||||
editor.setDocument('markdown', streamingSystemRole || '');
|
||||
} catch {
|
||||
// Ignore errors during streaming updates
|
||||
}
|
||||
}
|
||||
}, [editor, editorInit, streamingSystemRole, streamingInProgress]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editorInit || !editor || contentInit) return;
|
||||
// Don't init if streaming is in progress
|
||||
if (streamingInProgress) return;
|
||||
try {
|
||||
if (editorData) {
|
||||
editor.setDocument('json', editorData);
|
||||
} else if (systemRole) {
|
||||
editor.setDocument('markdown', systemRole);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}, [editorInit, contentInit, editor, editorData, systemRole, streamingInProgress]);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Editor
|
||||
content={initialLoad}
|
||||
editor={editor!}
|
||||
lineEmptyPlaceholder={t('settingAgent.prompt.placeholder')}
|
||||
mentionOption={mentionOptions}
|
||||
onInit={() => setEditorInit(true)}
|
||||
onTextChange={handleChange}
|
||||
placeholder={t('settingAgent.prompt.templatePlaceholder')}
|
||||
plugins={[
|
||||
ReactListPlugin,
|
||||
ReactCodePlugin,
|
||||
ReactCodemirrorPlugin,
|
||||
ReactHRPlugin,
|
||||
ReactLinkHighlightPlugin,
|
||||
ReactTablePlugin,
|
||||
ReactMathPlugin,
|
||||
ReactMentionPlugin,
|
||||
Editor.withProps(ReactToolbarPlugin, {
|
||||
children: <TypoBar />,
|
||||
}),
|
||||
]}
|
||||
slashOption={{
|
||||
items: slashItems,
|
||||
}}
|
||||
style={{
|
||||
paddingBottom: 64,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default EditorCanvas;
|
||||
@@ -1,139 +0,0 @@
|
||||
import {
|
||||
INSERT_CHECK_LIST_COMMAND,
|
||||
INSERT_HEADING_COMMAND,
|
||||
INSERT_HORIZONTAL_RULE_COMMAND,
|
||||
INSERT_MATH_COMMAND,
|
||||
INSERT_ORDERED_LIST_COMMAND,
|
||||
INSERT_TABLE_COMMAND,
|
||||
INSERT_UNORDERED_LIST_COMMAND,
|
||||
type SlashOptions,
|
||||
} from '@lobehub/editor';
|
||||
import { Text } from '@lobehub/ui';
|
||||
import {
|
||||
Heading1Icon,
|
||||
Heading2Icon,
|
||||
Heading3Icon,
|
||||
ListIcon,
|
||||
ListOrderedIcon,
|
||||
ListTodoIcon,
|
||||
MinusIcon,
|
||||
SigmaIcon,
|
||||
SquareDashedBottomCodeIcon,
|
||||
Table2Icon,
|
||||
} from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useProfileStore } from '../store';
|
||||
|
||||
export const useSlashItems = (): SlashOptions['items'] => {
|
||||
const { t } = useTranslation('editor');
|
||||
const editorState = useProfileStore((s) => s.editorState);
|
||||
return useMemo(() => {
|
||||
const data: SlashOptions['items'] = [
|
||||
{
|
||||
icon: Heading1Icon,
|
||||
key: 'h1',
|
||||
label: t('slash.h1'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h1' });
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: Heading2Icon,
|
||||
key: 'h2',
|
||||
label: t('slash.h2'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h2' });
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: Heading3Icon,
|
||||
key: 'h3',
|
||||
label: t('slash.h3'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h3' });
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
icon: ListTodoIcon,
|
||||
key: 'tl',
|
||||
label: t('typobar.taskList'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined);
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: ListIcon,
|
||||
key: 'ul',
|
||||
label: t('typobar.bulletList'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: ListOrderedIcon,
|
||||
key: 'ol',
|
||||
label: t('typobar.numberList'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
icon: MinusIcon,
|
||||
key: 'hr',
|
||||
label: t('slash.hr'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, {});
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: Table2Icon,
|
||||
key: 'table',
|
||||
label: t('slash.table'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_TABLE_COMMAND, { columns: '3', rows: '3' });
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: SquareDashedBottomCodeIcon,
|
||||
key: 'codeblock',
|
||||
label: t('typobar.codeblock'),
|
||||
onSelect: () => {
|
||||
editorState.codeblock();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: SigmaIcon,
|
||||
key: 'tex',
|
||||
label: t('slash.tex'),
|
||||
onSelect: (editor) => {
|
||||
editor.dispatchCommand(INSERT_MATH_COMMAND, { code: 'x^2 + y^2 = z^2' });
|
||||
queueMicrotask(() => {
|
||||
editor.focus();
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
return data.map((item) => {
|
||||
if (item.type === 'divider') return item;
|
||||
return {
|
||||
...item,
|
||||
extra: (
|
||||
<Text code fontSize={12} type={'secondary'}>
|
||||
{item.key}
|
||||
</Text>
|
||||
),
|
||||
style: {
|
||||
minWidth: 200,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [t]);
|
||||
};
|
||||
+22
-29
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR, EDITOR_DEBOUNCE_TIME } from '@lobechat/const';
|
||||
import { EDITOR_DEBOUNCE_TIME } from '@lobechat/const';
|
||||
import { Block, Flexbox, Icon, Input, Skeleton, Tooltip } from '@lobehub/ui';
|
||||
import { useDebounceFn } from 'ahooks';
|
||||
import { message } from 'antd';
|
||||
@@ -9,11 +9,9 @@ import { PaletteIcon } from 'lucide-react';
|
||||
import { Suspense, memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SupervisorAvatar from '@/app/[variants]/(main)/group/features/GroupAvatar';
|
||||
import GroupAvatar from '@/app/[variants]/(main)/group/features/GroupAvatar';
|
||||
import EmojiPicker from '@/components/EmojiPicker';
|
||||
import BackgroundSwatches from '@/features/AgentSetting/AgentMeta/BackgroundSwatches';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentSelectors } from '@/store/agent/selectors';
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
||||
import { useFileStore } from '@/store/file';
|
||||
@@ -22,23 +20,19 @@ import { globalGeneralSelectors } from '@/store/global/selectors';
|
||||
|
||||
const MAX_AVATAR_SIZE = 1024 * 1024; // 1MB limit for server actions
|
||||
|
||||
const AgentHeader = memo(() => {
|
||||
const GroupHeader = memo(() => {
|
||||
const { t } = useTranslation(['setting', 'common']);
|
||||
const locale = useGlobalStore(globalGeneralSelectors.currentLanguage);
|
||||
|
||||
// Get title from agentGroup store (groupMap.title)
|
||||
// Get group meta from agentGroup store
|
||||
const groupMeta = useAgentGroupStore(agentGroupSelectors.currentGroupMeta, isEqual);
|
||||
const updateGroupMeta = useAgentGroupStore((s) => s.updateGroupMeta);
|
||||
|
||||
// Get avatar/backgroundColor from supervisor agent (useAgentStore)
|
||||
const agentMeta = useAgentStore(agentSelectors.currentAgentMeta, isEqual);
|
||||
const updateAgentMeta = useAgentStore((s) => s.updateAgentMeta);
|
||||
|
||||
// File upload
|
||||
const uploadWithProgress = useFileStore((s) => s.uploadWithProgress);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
// Local state for inputs (to avoid stuttering during typing)
|
||||
// Local state for inputs
|
||||
const [localTitle, setLocalTitle] = useState(groupMeta.title || '');
|
||||
|
||||
// Sync local state when meta changes from external source
|
||||
@@ -46,7 +40,7 @@ const AgentHeader = memo(() => {
|
||||
setLocalTitle(groupMeta.title || '');
|
||||
}, [groupMeta.title]);
|
||||
|
||||
// Debounced save for title - save to group store
|
||||
// Debounced save for title
|
||||
const { run: debouncedSaveTitle } = useDebounceFn(
|
||||
(value: string) => {
|
||||
updateGroupMeta({ title: value });
|
||||
@@ -54,9 +48,9 @@ const AgentHeader = memo(() => {
|
||||
{ wait: EDITOR_DEBOUNCE_TIME },
|
||||
);
|
||||
|
||||
// Handle avatar change (immediate save) - save to agent store (supervisor agent)
|
||||
// Handle avatar change (immediate save)
|
||||
const handleAvatarChange = (emoji: string) => {
|
||||
updateAgentMeta({ avatar: emoji });
|
||||
updateGroupMeta({ avatar: emoji });
|
||||
};
|
||||
|
||||
// Handle avatar upload
|
||||
@@ -70,26 +64,25 @@ const AgentHeader = memo(() => {
|
||||
setUploading(true);
|
||||
try {
|
||||
const result = await uploadWithProgress({ file });
|
||||
console.log('result', result);
|
||||
if (result?.url) {
|
||||
updateAgentMeta({ avatar: result.url });
|
||||
updateGroupMeta({ avatar: result.url });
|
||||
}
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
},
|
||||
[uploadWithProgress, updateAgentMeta, t],
|
||||
[uploadWithProgress, updateGroupMeta, t],
|
||||
);
|
||||
|
||||
// Handle avatar delete
|
||||
const handleAvatarDelete = useCallback(() => {
|
||||
updateAgentMeta({ avatar: undefined });
|
||||
}, [updateAgentMeta]);
|
||||
updateGroupMeta({ avatar: undefined });
|
||||
}, [updateGroupMeta]);
|
||||
|
||||
// Handle background color change (immediate save) - save to agent store (supervisor agent)
|
||||
// Handle background color change
|
||||
const handleBackgroundColorChange = (color?: string) => {
|
||||
if (color !== undefined) {
|
||||
updateAgentMeta({ backgroundColor: color });
|
||||
updateGroupMeta({ backgroundColor: color });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -106,20 +99,20 @@ const AgentHeader = memo(() => {
|
||||
}}
|
||||
>
|
||||
<EmojiPicker
|
||||
allowDelete={!!agentMeta.avatar}
|
||||
allowDelete={!!groupMeta.avatar}
|
||||
allowUpload
|
||||
background={
|
||||
agentMeta.backgroundColor && agentMeta.backgroundColor !== 'rgba(0,0,0,0)'
|
||||
? agentMeta.backgroundColor
|
||||
groupMeta.backgroundColor && groupMeta.backgroundColor !== 'rgba(0,0,0,0)'
|
||||
? groupMeta.backgroundColor
|
||||
: undefined
|
||||
}
|
||||
customRender={
|
||||
agentMeta.avatar && agentMeta.avatar !== DEFAULT_AVATAR
|
||||
groupMeta.avatar
|
||||
? undefined
|
||||
: () => {
|
||||
return (
|
||||
<Block clickable height={72} width={72}>
|
||||
<SupervisorAvatar size={72} />
|
||||
<GroupAvatar size={72} />
|
||||
</Block>
|
||||
);
|
||||
}
|
||||
@@ -146,7 +139,7 @@ const AgentHeader = memo(() => {
|
||||
onChange={handleBackgroundColorChange}
|
||||
shape={'square'}
|
||||
size={38}
|
||||
value={agentMeta.backgroundColor}
|
||||
value={groupMeta.backgroundColor}
|
||||
/>
|
||||
</Suspense>
|
||||
</Flexbox>
|
||||
@@ -164,7 +157,7 @@ const AgentHeader = memo(() => {
|
||||
}}
|
||||
shape={'square'}
|
||||
size={72}
|
||||
value={agentMeta.avatar}
|
||||
value={groupMeta.avatar}
|
||||
/>
|
||||
<Input
|
||||
onChange={(e) => {
|
||||
@@ -185,4 +178,4 @@ const AgentHeader = memo(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export default AgentHeader;
|
||||
export default GroupHeader;
|
||||
@@ -0,0 +1,96 @@
|
||||
'use client';
|
||||
|
||||
import { Button, Flexbox } from '@lobehub/ui';
|
||||
import { Divider } from 'antd';
|
||||
import { PlayIcon } from 'lucide-react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import { EditorCanvas } from '@/features/EditorCanvas';
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
||||
import { useGroupProfileStore } from '@/store/groupProfile';
|
||||
|
||||
import AutoSaveHint from '../Header/AutoSaveHint';
|
||||
import GroupHeader from './GroupHeader';
|
||||
|
||||
const GroupProfile = memo(() => {
|
||||
const { t } = useTranslation(['setting', 'chat']);
|
||||
const groupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
|
||||
const currentGroup = useAgentGroupStore(agentGroupSelectors.currentGroup);
|
||||
const updateGroup = useAgentGroupStore((s) => s.updateGroup);
|
||||
const router = useQueryRoute();
|
||||
|
||||
const editor = useGroupProfileStore((s) => s.editor);
|
||||
const handleContentChange = useGroupProfileStore((s) => s.handleContentChange);
|
||||
|
||||
// Create save callback that captures latest groupId
|
||||
const saveContent = useCallback(
|
||||
async (payload: { content: string; editorData: Record<string, any> }) => {
|
||||
if (!groupId) return;
|
||||
await updateGroup(groupId, {
|
||||
content: payload.content,
|
||||
editorData: payload.editorData,
|
||||
});
|
||||
},
|
||||
[updateGroup, groupId],
|
||||
);
|
||||
|
||||
const onContentChange = useCallback(() => {
|
||||
handleContentChange(saveContent);
|
||||
}, [handleContentChange, saveContent]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flexbox
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
style={{ cursor: 'default', marginBottom: 12 }}
|
||||
>
|
||||
<Flexbox height={66} width={'100%'}>
|
||||
<Flexbox paddingBlock={12}>
|
||||
<AutoSaveHint />
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
{/* Header: Group Avatar + Title */}
|
||||
<GroupHeader />
|
||||
{/* Start Conversation Button */}
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
gap={8}
|
||||
horizontal
|
||||
justify={'flex-start'}
|
||||
style={{ marginTop: 16 }}
|
||||
>
|
||||
<Button
|
||||
icon={PlayIcon}
|
||||
onClick={() => {
|
||||
if (!groupId) return;
|
||||
router.push(urlJoin('/group', groupId));
|
||||
}}
|
||||
type={'primary'}
|
||||
>
|
||||
{t('startConversation')}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
<Divider />
|
||||
{/* Group Content Editor */}
|
||||
<EditorCanvas
|
||||
editor={editor}
|
||||
editorData={{
|
||||
content: currentGroup?.content ?? undefined,
|
||||
editorData: currentGroup?.editorData,
|
||||
}}
|
||||
key={groupId}
|
||||
onContentChange={onContentChange}
|
||||
placeholder={t('group.profile.contentPlaceholder', { ns: 'chat' })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default GroupProfile;
|
||||
@@ -3,12 +3,11 @@ import { BotMessageSquareIcon } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
||||
|
||||
import { useProfileStore } from '../store';
|
||||
import { useGroupProfileStore } from '@/store/groupProfile';
|
||||
|
||||
const AgentBuilderToggle = memo(() => {
|
||||
const chatPanelExpanded = useProfileStore((s) => s.chatPanelExpanded);
|
||||
const setChatPanelExpanded = useProfileStore((s) => s.setChatPanelExpanded);
|
||||
const chatPanelExpanded = useGroupProfileStore((s) => s.chatPanelExpanded);
|
||||
const setChatPanelExpanded = useGroupProfileStore((s) => s.setChatPanelExpanded);
|
||||
|
||||
return (
|
||||
<ActionIcon
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import AutoSaveHintBase from '@/components/Editor/AutoSaveHint';
|
||||
import { useGroupProfileStore } from '@/store/groupProfile';
|
||||
import { selectors } from '@/store/groupProfile/selectors';
|
||||
|
||||
/**
|
||||
* AutoSaveHint - Save status indicator for group settings
|
||||
* TODO: Add saveStatus and lastUpdatedTime to agentGroupStore when needed
|
||||
*/
|
||||
const AutoSaveHint = memo(() => {
|
||||
// Group profile currently doesn't track save status
|
||||
// Return idle state for now
|
||||
return <AutoSaveHintBase lastUpdatedTime={null} saveStatus={'idle'} />;
|
||||
const activeTabId = useGroupProfileStore((s) => s.activeTabId);
|
||||
const saveState = useGroupProfileStore(selectors.getSaveState(activeTabId));
|
||||
|
||||
return (
|
||||
<AutoSaveHintBase
|
||||
lastUpdatedTime={saveState.lastUpdatedTime}
|
||||
saveStatus={saveState.saveStatus}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default AutoSaveHint;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
'use client';
|
||||
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { ReactNode, memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar: cv }) => ({
|
||||
addButton: css`
|
||||
cursor: pointer;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
|
||||
color: ${cv.colorTextTertiary};
|
||||
|
||||
background: transparent;
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: ${cv.colorTextSecondary};
|
||||
background: ${cv.colorFillTertiary};
|
||||
}
|
||||
`,
|
||||
container: css`
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
`,
|
||||
externalTag: css`
|
||||
flex-shrink: 0;
|
||||
|
||||
padding-block: 1px;
|
||||
padding-inline: 4px;
|
||||
border-radius: 4px;
|
||||
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
|
||||
background: ${cv.colorFillSecondary};
|
||||
`,
|
||||
tab: css`
|
||||
cursor: pointer;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
|
||||
height: 32px;
|
||||
padding-block: 6px;
|
||||
padding-inline: 12px;
|
||||
border-radius: 8px;
|
||||
|
||||
color: ${cv.colorTextTertiary};
|
||||
|
||||
background: transparent;
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: ${cv.colorTextSecondary};
|
||||
background: ${cv.colorFillTertiary};
|
||||
}
|
||||
`,
|
||||
tabActive: css`
|
||||
color: ${cv.colorText};
|
||||
background: ${cv.colorFillTertiary};
|
||||
|
||||
&:hover {
|
||||
color: ${cv.colorText};
|
||||
background: ${cv.colorFillTertiary};
|
||||
}
|
||||
`,
|
||||
tabTitle: css`
|
||||
overflow: hidden;
|
||||
|
||||
max-width: 120px;
|
||||
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export interface ChromeTabItem {
|
||||
avatar?: string;
|
||||
icon?: ReactNode;
|
||||
id: string;
|
||||
isExternal?: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface ChromeTabsProps {
|
||||
activeId: string;
|
||||
items: ChromeTabItem[];
|
||||
onAdd?: () => void;
|
||||
onChange: (id: string) => void;
|
||||
}
|
||||
|
||||
const ChromeTabs = memo<ChromeTabsProps>(({ items, activeId, onChange, onAdd }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{items.map((item) => {
|
||||
const isActive = item.id === activeId;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(styles.tab, isActive && styles.tabActive)}
|
||||
key={item.id}
|
||||
onClick={() => onChange(item.id)}
|
||||
>
|
||||
<Flexbox align="center" gap={6} horizontal>
|
||||
{item.icon ? (
|
||||
item.icon
|
||||
) : item.avatar ? (
|
||||
<Avatar avatar={item.avatar} size={18} />
|
||||
) : null}
|
||||
<span className={styles.tabTitle}>{item.title}</span>
|
||||
{item.isExternal && (
|
||||
<span className={styles.externalTag}>{t('group.profile.external')}</span>
|
||||
)}
|
||||
</Flexbox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{onAdd && (
|
||||
<div className={styles.addButton} onClick={onAdd}>
|
||||
<Plus size={16} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default ChromeTabs;
|
||||
@@ -1,23 +1,117 @@
|
||||
import { memo } from 'react';
|
||||
'use client';
|
||||
|
||||
import NavHeader from '@/features/NavHeader';
|
||||
import WideScreenButton from '@/features/WideScreenContainer/WideScreenButton';
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { Crown, Users } from 'lucide-react';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import AddGroupMemberModal from '@/app/[variants]/(main)/group/_layout/Sidebar/AddGroupMemberModal';
|
||||
import { parseAsString, useQueryState } from '@/hooks/useQueryParam';
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
||||
|
||||
import AgentBuilderToggle from './AgentBuilderToggle';
|
||||
import AutoSaveHint from './AutoSaveHint';
|
||||
import ChromeTabs, { type ChromeTabItem } from './ChromeTabs';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
header: css`
|
||||
overflow: hidden;
|
||||
|
||||
flex: none;
|
||||
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
padding-block: 8px;
|
||||
padding-inline: 12px;
|
||||
|
||||
border-block-end: 1px solid ${token.colorBorderSecondary};
|
||||
`,
|
||||
tabsWrapper: css`
|
||||
overflow-x: auto;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation('chat');
|
||||
const { styles } = useStyles();
|
||||
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
|
||||
const members = useAgentGroupStore(agentGroupSelectors.currentGroupAgents);
|
||||
const activeGroupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
|
||||
const addAgentsToGroup = useAgentGroupStore((s) => s.addAgentsToGroup);
|
||||
|
||||
// Use URL query param for selected tab
|
||||
const [selectedTabId, setSelectedTabId] = useQueryState(
|
||||
'tab',
|
||||
parseAsString.withDefault('group'),
|
||||
);
|
||||
|
||||
const existingMemberIds = useMemo(() => members.map((a) => a.id), [members]);
|
||||
|
||||
const tabItems = useMemo<ChromeTabItem[]>(() => {
|
||||
const items: ChromeTabItem[] = [
|
||||
{
|
||||
icon: <Users size={16} />,
|
||||
id: 'group',
|
||||
title: t('group.profile.groupSettings'),
|
||||
},
|
||||
];
|
||||
|
||||
// Add agent tabs
|
||||
for (const agent of members) {
|
||||
items.push({
|
||||
avatar: agent.isSupervisor ? undefined : agent.avatar || undefined,
|
||||
icon: agent.isSupervisor ? <Crown size={16} /> : undefined,
|
||||
id: agent.id,
|
||||
isExternal: !agent.isSupervisor && !agent.virtual,
|
||||
title: agent.isSupervisor ? t('group.profile.supervisor') : agent.title || 'Untitled Agent',
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [members, t]);
|
||||
|
||||
const handleAddMembers = async (agentIds: string[]) => {
|
||||
if (!activeGroupId) return;
|
||||
await addAgentsToGroup(activeGroupId, agentIds);
|
||||
setShowAddModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<NavHeader
|
||||
left={<AutoSaveHint />}
|
||||
right={
|
||||
<>
|
||||
<WideScreenButton />
|
||||
<>
|
||||
<Flexbox align="center" className={styles.header} horizontal justify="space-between">
|
||||
<div className={styles.tabsWrapper}>
|
||||
<ChromeTabs
|
||||
activeId={selectedTabId}
|
||||
items={tabItems}
|
||||
onAdd={() => setShowAddModal(true)}
|
||||
onChange={setSelectedTabId}
|
||||
/>
|
||||
</div>
|
||||
<Flexbox align="center" flex="none" gap={8} horizontal style={{ marginInlineStart: 12 }}>
|
||||
<AgentBuilderToggle />
|
||||
{/* TODO: Add GroupPublishButton when group publishing is supported */}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
{activeGroupId && (
|
||||
<AddGroupMemberModal
|
||||
existingMembers={existingMemberIds}
|
||||
groupId={activeGroupId}
|
||||
onCancel={() => setShowAddModal(false)}
|
||||
onConfirm={handleAddMembers}
|
||||
open={showAddModal}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR, EDITOR_DEBOUNCE_TIME } from '@lobechat/const';
|
||||
import { Block, Flexbox, Icon, Input, Skeleton, Tooltip } from '@lobehub/ui';
|
||||
import { useDebounceFn } from 'ahooks';
|
||||
import { message } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { PaletteIcon } from 'lucide-react';
|
||||
import { Suspense, memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import SupervisorAvatar from '@/app/[variants]/(main)/group/features/GroupAvatar';
|
||||
import EmojiPicker from '@/components/EmojiPicker';
|
||||
import BackgroundSwatches from '@/features/AgentSetting/AgentMeta/BackgroundSwatches';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentSelectors } from '@/store/agent/selectors';
|
||||
import { useFileStore } from '@/store/file';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { globalGeneralSelectors } from '@/store/global/selectors';
|
||||
import { useGroupProfileStore } from '@/store/groupProfile';
|
||||
|
||||
const MAX_AVATAR_SIZE = 1024 * 1024; // 1MB limit for server actions
|
||||
|
||||
interface AgentHeaderProps {
|
||||
/**
|
||||
* When true, shows fixed title (supervisor) and disables avatar editing
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const AgentHeader = memo<AgentHeaderProps>(({ readOnly }) => {
|
||||
const { t } = useTranslation(['setting', 'common', 'chat']);
|
||||
const locale = useGlobalStore(globalGeneralSelectors.currentLanguage);
|
||||
|
||||
// Get agentId from profile store
|
||||
const agentId = useGroupProfileStore((s) => s.activeTabId);
|
||||
|
||||
// Get agent meta by agentId
|
||||
const agentMeta = useAgentStore(agentSelectors.getAgentMetaById(agentId), isEqual);
|
||||
const optimisticUpdateAgentMeta = useAgentStore((s) => s.optimisticUpdateAgentMeta);
|
||||
|
||||
// File upload
|
||||
const uploadWithProgress = useFileStore((s) => s.uploadWithProgress);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
// Local state for inputs (to avoid stuttering during typing)
|
||||
const [localTitle, setLocalTitle] = useState(agentMeta.title || '');
|
||||
|
||||
// Sync local state when meta changes from external source
|
||||
useEffect(() => {
|
||||
setLocalTitle(agentMeta.title || '');
|
||||
}, [agentMeta.title]);
|
||||
|
||||
// Debounced save for title - save to agent store
|
||||
const { run: debouncedSaveTitle } = useDebounceFn(
|
||||
(value: string) => {
|
||||
optimisticUpdateAgentMeta(agentId, { title: value });
|
||||
},
|
||||
{ wait: EDITOR_DEBOUNCE_TIME },
|
||||
);
|
||||
|
||||
// Handle avatar change (immediate save) - save to agent store (supervisor agent)
|
||||
const handleAvatarChange = (emoji: string) => {
|
||||
optimisticUpdateAgentMeta(agentId, { avatar: emoji });
|
||||
};
|
||||
|
||||
// Handle avatar upload
|
||||
const handleAvatarUpload = useCallback(
|
||||
async (file: File) => {
|
||||
if (file.size > MAX_AVATAR_SIZE) {
|
||||
message.error(t('settingAgent.avatar.sizeExceeded', { ns: 'setting' }));
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
try {
|
||||
const result = await uploadWithProgress({ file });
|
||||
console.log('result', result);
|
||||
if (result?.url) {
|
||||
optimisticUpdateAgentMeta(agentId, { avatar: result.url });
|
||||
}
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
},
|
||||
[uploadWithProgress, optimisticUpdateAgentMeta, agentId, t],
|
||||
);
|
||||
|
||||
// Handle avatar delete
|
||||
const handleAvatarDelete = useCallback(() => {
|
||||
optimisticUpdateAgentMeta(agentId, { avatar: undefined });
|
||||
}, [optimisticUpdateAgentMeta, agentId]);
|
||||
|
||||
// Handle background color change (immediate save) - save to agent store (supervisor agent)
|
||||
const handleBackgroundColorChange = (color?: string) => {
|
||||
if (color !== undefined) {
|
||||
optimisticUpdateAgentMeta(agentId, { backgroundColor: color });
|
||||
}
|
||||
};
|
||||
|
||||
// ReadOnly mode: show fixed avatar and title (for supervisor)
|
||||
if (readOnly) {
|
||||
return (
|
||||
<Flexbox
|
||||
gap={16}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
paddingBlock={16}
|
||||
style={{
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
<Block height={72} width={72}>
|
||||
<SupervisorAvatar size={72} />
|
||||
</Block>
|
||||
<Flexbox
|
||||
style={{
|
||||
fontSize: 36,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{t('group.profile.supervisor', { ns: 'chat' })}
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
gap={16}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
paddingBlock={16}
|
||||
style={{
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
<EmojiPicker
|
||||
allowDelete={!!agentMeta.avatar}
|
||||
allowUpload
|
||||
background={
|
||||
agentMeta.backgroundColor && agentMeta.backgroundColor !== 'rgba(0,0,0,0)'
|
||||
? agentMeta.backgroundColor
|
||||
: undefined
|
||||
}
|
||||
customRender={
|
||||
agentMeta.avatar && agentMeta.avatar !== DEFAULT_AVATAR
|
||||
? undefined
|
||||
: () => {
|
||||
return (
|
||||
<Block clickable height={72} width={72}>
|
||||
<SupervisorAvatar size={72} />
|
||||
</Block>
|
||||
);
|
||||
}
|
||||
}
|
||||
customTabs={[
|
||||
{
|
||||
label: (
|
||||
<Tooltip title={t('settingAgent.backgroundColor.title', { ns: 'setting' })}>
|
||||
<Icon icon={PaletteIcon} size={{ size: 20, strokeWidth: 2.5 }} />
|
||||
</Tooltip>
|
||||
),
|
||||
render: () => (
|
||||
<Flexbox padding={8} width={332}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<Flexbox gap={8}>
|
||||
<Skeleton.Button block style={{ height: 38 }} />
|
||||
<Skeleton.Button block style={{ height: 38 }} />
|
||||
</Flexbox>
|
||||
}
|
||||
>
|
||||
<BackgroundSwatches
|
||||
gap={8}
|
||||
onChange={handleBackgroundColorChange}
|
||||
shape={'square'}
|
||||
size={38}
|
||||
value={agentMeta.backgroundColor}
|
||||
/>
|
||||
</Suspense>
|
||||
</Flexbox>
|
||||
),
|
||||
value: 'background',
|
||||
},
|
||||
]}
|
||||
loading={uploading}
|
||||
locale={locale}
|
||||
onChange={handleAvatarChange}
|
||||
onDelete={handleAvatarDelete}
|
||||
onUpload={handleAvatarUpload}
|
||||
popupProps={{
|
||||
placement: 'bottomLeft',
|
||||
}}
|
||||
shape={'square'}
|
||||
size={72}
|
||||
value={agentMeta.avatar}
|
||||
/>
|
||||
<Input
|
||||
onChange={(e) => {
|
||||
setLocalTitle(e.target.value);
|
||||
debouncedSaveTitle(e.target.value);
|
||||
}}
|
||||
placeholder={t('settingAgent.name.placeholder', { ns: 'setting' })}
|
||||
style={{
|
||||
fontSize: 36,
|
||||
fontWeight: 600,
|
||||
padding: 0,
|
||||
width: '100%',
|
||||
}}
|
||||
value={localTitle}
|
||||
variant={'borderless'}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
export default AgentHeader;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user