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:
Arvin Xu
2026-01-13 16:07:30 +08:00
committed by GitHub
parent 5c3dc7493e
commit 9012b40230
164 changed files with 5209 additions and 2352 deletions
+7
View File
@@ -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",
+22 -1
View File
@@ -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",
+7
View File
@@ -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": "成员设置",
+22 -1
View File
@@ -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 });
}
}
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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,
};
@@ -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;
@@ -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;
@@ -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()}
@@ -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,
)}
>
+1 -1
View File
@@ -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();
+8
View File
@@ -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]);
};
@@ -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