mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-15 20:16:02 +00:00
💄 style: support streaming and display ui for group mode
This commit is contained in:
@@ -51,6 +51,8 @@
|
||||
"builtins.lobe-group-management.apiName.speak": "Designated member speaks",
|
||||
"builtins.lobe-group-management.apiName.summarize": "Summarize conversation",
|
||||
"builtins.lobe-group-management.apiName.vote": "Start vote",
|
||||
"builtins.lobe-group-management.inspector.broadcast.title": "Following Agents speak:",
|
||||
"builtins.lobe-group-management.inspector.speak.title": "Designated Agent speaks:",
|
||||
"builtins.lobe-group-management.title": "Group Coordinator",
|
||||
"builtins.lobe-gtd.apiName.clearTodos": "Clear todos",
|
||||
"builtins.lobe-gtd.apiName.clearTodos.modeAll": "all",
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
"builtins.lobe-group-management.apiName.speak": "指定成员发言",
|
||||
"builtins.lobe-group-management.apiName.summarize": "总结对话",
|
||||
"builtins.lobe-group-management.apiName.vote": "发起投票",
|
||||
"builtins.lobe-group-management.inspector.broadcast.title": "以下 Agent 发言:",
|
||||
"builtins.lobe-group-management.inspector.speak.title": "指定 Agent 发言:",
|
||||
"builtins.lobe-group-management.title": "群组协调",
|
||||
"builtins.lobe-gtd.apiName.clearTodos": "清除待办",
|
||||
"builtins.lobe-gtd.apiName.clearTodos.modeAll": "全部",
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@lobechat/const';
|
||||
import type { AgentItem, BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx, useTheme } 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 { shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { BroadcastParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`,
|
||||
title: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const BroadcastInspector = memo<BuiltinInspectorProps<BroadcastParams>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const agentIds = args?.agentIds || partialArgs?.agentIds || [];
|
||||
|
||||
// Get active group ID and agents from store
|
||||
const activeGroupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
|
||||
const groupAgents = useAgentGroupStore((s) =>
|
||||
activeGroupId ? agentGroupSelectors.getGroupAgents(activeGroupId)(s) : [],
|
||||
);
|
||||
const theme = useTheme();
|
||||
|
||||
// Get agent details for the broadcast targets
|
||||
const agents = useMemo(() => {
|
||||
if (!agentIds.length || !groupAgents.length) return [];
|
||||
return agentIds
|
||||
.map((id) => groupAgents.find((agent) => agent.id === id))
|
||||
.filter((agent): agent is AgentItem => !!agent);
|
||||
}, [agentIds, groupAgents]);
|
||||
|
||||
// Transform agents to Avatar.Group format
|
||||
const avatarItems = useMemo(
|
||||
() =>
|
||||
agents.map((agent) => ({
|
||||
avatar: agent.avatar || DEFAULT_AVATAR,
|
||||
background: agent.backgroundColor || theme.colorBgContainer,
|
||||
key: agent.id,
|
||||
title: agent.title || undefined,
|
||||
})),
|
||||
[agents],
|
||||
);
|
||||
|
||||
if (isArgumentsStreaming && agents.length === 0) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-management.apiName.broadcast')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
gap={8}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.title}>
|
||||
{t('builtins.lobe-group-management.inspector.broadcast.title')}
|
||||
</span>
|
||||
{avatarItems.length > 0 && <Avatar.Group items={avatarItems} shape={'square'} size={24} />}
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BroadcastInspector.displayName = 'BroadcastInspector';
|
||||
|
||||
export default BroadcastInspector;
|
||||
@@ -0,0 +1,87 @@
|
||||
'use client';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@lobechat/const';
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx, useTheme } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAgentGroupStore } from '@/store/agentGroup';
|
||||
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
||||
import { shinyTextStyles } from '@/styles';
|
||||
|
||||
import type { SpeakParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
agentName: css`
|
||||
padding-block-end: 1px;
|
||||
color: ${cssVar.colorText};
|
||||
background: linear-gradient(to top, ${cssVar.colorPrimaryBg} 40%, transparent 40%);
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`,
|
||||
title: css`
|
||||
flex-shrink: 0;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
}));
|
||||
|
||||
export const SpeakInspector = memo<BuiltinInspectorProps<SpeakParams>>(
|
||||
({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
|
||||
const agentId = args?.agentId || partialArgs?.agentId;
|
||||
|
||||
// Get active group ID and agent from store
|
||||
const activeGroupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
|
||||
const agent = useAgentGroupStore((s) =>
|
||||
activeGroupId && agentId
|
||||
? agentGroupSelectors.getAgentByIdFromGroup(activeGroupId, agentId)(s)
|
||||
: undefined,
|
||||
);
|
||||
const theme = useTheme();
|
||||
|
||||
if (isArgumentsStreaming && !agent) {
|
||||
return (
|
||||
<div className={cx(styles.root, shinyTextStyles.shinyText)}>
|
||||
<span>{t('builtins.lobe-group-management.apiName.speak')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const agentName = agent?.title || agentId;
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={cx(styles.root, isArgumentsStreaming && shinyTextStyles.shinyText)}
|
||||
gap={8}
|
||||
horizontal
|
||||
>
|
||||
<span className={styles.title}>
|
||||
{t('builtins.lobe-group-management.inspector.speak.title')}
|
||||
</span>
|
||||
{agent && (
|
||||
<Avatar
|
||||
avatar={agent.avatar || DEFAULT_AVATAR}
|
||||
background={agent.backgroundColor || theme.colorBgContainer}
|
||||
shape={'square'}
|
||||
size={24}
|
||||
title={agent.title || undefined}
|
||||
/>
|
||||
)}
|
||||
{agentName && <span className={styles.agentName}>{agentName}</span>}
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SpeakInspector.displayName = 'SpeakInspector';
|
||||
|
||||
export default SpeakInspector;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { type BuiltinInspector } from '@lobechat/types';
|
||||
|
||||
import { GroupManagementApiName } from '../../types';
|
||||
import { BroadcastInspector } from './Broadcast';
|
||||
import { SpeakInspector } from './Speak';
|
||||
|
||||
/**
|
||||
* Group Management Inspector Components Registry
|
||||
*
|
||||
* Inspector components customize the title/header area
|
||||
* of tool calls in the conversation UI.
|
||||
*/
|
||||
export const GroupManagementInspectors: Record<string, BuiltinInspector> = {
|
||||
[GroupManagementApiName.broadcast]: BroadcastInspector as BuiltinInspector,
|
||||
[GroupManagementApiName.speak]: SpeakInspector as BuiltinInspector,
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { BroadcastParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
instruction: css`
|
||||
font-size: 13px;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const BroadcastRender = memo<BuiltinRenderProps<BroadcastParams>>(({ args }) => {
|
||||
const { instruction } = args || {};
|
||||
|
||||
if (!instruction) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.instruction}>
|
||||
<Markdown variant={'chat'}>{instruction}</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
BroadcastRender.displayName = 'BroadcastRender';
|
||||
|
||||
export default BroadcastRender;
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { SpeakParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
instruction: css`
|
||||
font-size: 13px;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const SpeakRender = memo<BuiltinRenderProps<SpeakParams>>(({ args }) => {
|
||||
const { instruction } = args || {};
|
||||
|
||||
if (!instruction) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.instruction}>
|
||||
<Markdown variant={'chat'}>{instruction}</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SpeakRender.displayName = 'SpeakRender';
|
||||
|
||||
export default SpeakRender;
|
||||
@@ -1,11 +1,17 @@
|
||||
import { GroupManagementApiName } from '../../types';
|
||||
import BroadcastRender from './Broadcast';
|
||||
import ExecuteTaskRender from './ExecuteTask';
|
||||
import SpeakRender from './Speak';
|
||||
|
||||
/**
|
||||
* Group Management Tool Render Components Registry
|
||||
*/
|
||||
export const GroupManagementRenders = {
|
||||
[GroupManagementApiName.broadcast]: BroadcastRender,
|
||||
[GroupManagementApiName.executeTask]: ExecuteTaskRender,
|
||||
[GroupManagementApiName.speak]: SpeakRender,
|
||||
};
|
||||
|
||||
export { default as BroadcastRender } from './Broadcast';
|
||||
export { default as ExecuteTaskRender } from './ExecuteTask';
|
||||
export { default as SpeakRender } from './Speak';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinStreamingProps } from '@lobechat/types';
|
||||
import { Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { BroadcastParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
instruction: css`
|
||||
font-size: 13px;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const BroadcastStreaming = memo<BuiltinStreamingProps<BroadcastParams>>(({ args }) => {
|
||||
const { instruction } = args || {};
|
||||
|
||||
if (!instruction) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.instruction}>
|
||||
<Markdown animated variant={'chat'}>
|
||||
{instruction}
|
||||
</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
BroadcastStreaming.displayName = 'BroadcastStreaming';
|
||||
|
||||
export default BroadcastStreaming;
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinStreamingProps } from '@lobechat/types';
|
||||
import { Markdown } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
import type { SpeakParams } from '../../../types';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
container: css`
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: ${cssVar.colorFillQuaternary};
|
||||
`,
|
||||
instruction: css`
|
||||
font-size: 13px;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
}));
|
||||
|
||||
export const SpeakStreaming = memo<BuiltinStreamingProps<SpeakParams>>(({ args }) => {
|
||||
const { instruction } = args || {};
|
||||
|
||||
if (!instruction) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.instruction}>
|
||||
<Markdown animated variant={'chat'}>
|
||||
{instruction}
|
||||
</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SpeakStreaming.displayName = 'SpeakStreaming';
|
||||
|
||||
export default SpeakStreaming;
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { BuiltinStreaming } from '@lobechat/types';
|
||||
|
||||
import { GroupManagementApiName } from '../../types';
|
||||
import { BroadcastStreaming } from './Broadcast';
|
||||
import { SpeakStreaming } from './Speak';
|
||||
|
||||
/**
|
||||
* Group Management Streaming Components Registry
|
||||
*
|
||||
* Streaming components render tool calls while they are
|
||||
* still executing, allowing real-time feedback to users.
|
||||
*/
|
||||
export const GroupManagementStreamings: Record<string, BuiltinStreaming> = {
|
||||
[GroupManagementApiName.broadcast]: BroadcastStreaming as BuiltinStreaming,
|
||||
[GroupManagementApiName.speak]: SpeakStreaming as BuiltinStreaming,
|
||||
};
|
||||
@@ -1,5 +1,11 @@
|
||||
// Inspector components (title/header area)
|
||||
export { GroupManagementInspectors } from './Inspector';
|
||||
|
||||
// Streaming components (real-time feedback)
|
||||
export { GroupManagementStreamings } from './Streaming';
|
||||
|
||||
// Render components (read-only snapshots)
|
||||
export { ExecuteTaskRender, GroupManagementRenders } from './Render';
|
||||
export { BroadcastRender, ExecuteTaskRender, GroupManagementRenders, SpeakRender } from './Render';
|
||||
|
||||
// Intervention components (interactive editing)
|
||||
export { ExecuteTaskIntervention, GroupManagementInterventions } from './Intervention';
|
||||
|
||||
+11
-2
@@ -150,7 +150,7 @@
|
||||
"id": "msg-supervisor-summary",
|
||||
"role": "supervisor",
|
||||
"agentId": "supervisor",
|
||||
"content": "## Summary\n\nBased on the discussion from our team:\n\n**Key Pros:**\n- Independent deployment and scaling\n- Technology flexibility\n- Clear service boundaries\n\n**Key Cons:**\n- Increased complexity in communication and infrastructure\n- Data consistency and monitoring challenges\n\nI recommend starting with a modular monolith and gradually extracting services as needed.",
|
||||
"content": "",
|
||||
"parentId": "msg-agent-architect-1",
|
||||
"model": "gpt-4",
|
||||
"provider": "openai",
|
||||
@@ -162,7 +162,16 @@
|
||||
},
|
||||
"metadata": {
|
||||
"isSupervisor": true
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "msg-supervisor-summary",
|
||||
"content": "## Summary\n\nBased on the discussion from our team:\n\n**Key Pros:**\n- Independent deployment and scaling\n- Technology flexibility\n- Clear service boundaries\n\n**Key Cons:**\n- Increased complexity in communication and infrastructure\n- Data consistency and monitoring challenges\n\nI recommend starting with a modular monolith and gradually extracting services as needed.",
|
||||
"metadata": {
|
||||
"isSupervisor": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"messageMap": {
|
||||
|
||||
@@ -154,6 +154,22 @@ export class FlatListBuilder {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Priority 2b: Supervisor message without tools (content-only)
|
||||
// Transform to supervisor role with content in children array
|
||||
if (
|
||||
message.role === 'assistant' &&
|
||||
message.metadata?.isSupervisor &&
|
||||
(!message.tools || message.tools.length === 0)
|
||||
) {
|
||||
const supervisorMessage = this.createSupervisorContentMessage(message);
|
||||
flatList.push(supervisorMessage);
|
||||
processedIds.add(message.id);
|
||||
|
||||
// Continue with children
|
||||
this.buildFlatListRecursive(message.id, flatList, processedIds, allMessages);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Priority 3a: Compare mode from user message metadata
|
||||
const childMessages = this.childrenMap.get(message.id) ?? [];
|
||||
if (this.isCompareMode(message) && childMessages.length > 1) {
|
||||
@@ -747,4 +763,88 @@ export class FlatListBuilder {
|
||||
},
|
||||
} as Message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supervisor virtual message for content-only supervisor messages
|
||||
* Moves content to children array similar to assistantGroup
|
||||
*/
|
||||
private createSupervisorContentMessage(message: Message): Message {
|
||||
// Prefer top-level usage/performance fields, fall back to metadata
|
||||
const { usage: metaUsage, performance: metaPerformance } =
|
||||
this.messageTransformer.splitMetadata(message.metadata);
|
||||
const msgUsage = message.usage || metaUsage;
|
||||
const msgPerformance = message.performance || metaPerformance;
|
||||
|
||||
// Extract non-usage/performance metadata fields
|
||||
const otherMetadata: Record<string, any> = {};
|
||||
if (message.metadata) {
|
||||
const usagePerformanceFields = new Set([
|
||||
'acceptedPredictionTokens',
|
||||
'cost',
|
||||
'duration',
|
||||
'inputAudioTokens',
|
||||
'inputCacheMissTokens',
|
||||
'inputCachedTokens',
|
||||
'inputCitationTokens',
|
||||
'inputImageTokens',
|
||||
'inputTextTokens',
|
||||
'inputWriteCacheTokens',
|
||||
'latency',
|
||||
'outputAudioTokens',
|
||||
'outputImageTokens',
|
||||
'outputReasoningTokens',
|
||||
'outputTextTokens',
|
||||
'rejectedPredictionTokens',
|
||||
'totalInputTokens',
|
||||
'totalOutputTokens',
|
||||
'totalTokens',
|
||||
'tps',
|
||||
'ttft',
|
||||
]);
|
||||
|
||||
Object.entries(message.metadata).forEach(([key, value]) => {
|
||||
if (!usagePerformanceFields.has(key)) {
|
||||
otherMetadata[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Create the child content block
|
||||
const childBlock: any = {
|
||||
content: message.content || '',
|
||||
id: message.id,
|
||||
};
|
||||
|
||||
if (message.error) childBlock.error = message.error;
|
||||
if (message.fileList && message.fileList.length > 0) childBlock.fileList = message.fileList;
|
||||
if (message.imageList && message.imageList.length > 0) childBlock.imageList = message.imageList;
|
||||
if (msgPerformance) childBlock.performance = msgPerformance;
|
||||
if (message.reasoning) childBlock.reasoning = message.reasoning;
|
||||
if (msgUsage) childBlock.usage = msgUsage;
|
||||
if (Object.keys(otherMetadata).length > 0) {
|
||||
childBlock.metadata = otherMetadata;
|
||||
}
|
||||
|
||||
const result: Message = {
|
||||
...message,
|
||||
children: [childBlock],
|
||||
content: '',
|
||||
role: 'supervisor' as any,
|
||||
};
|
||||
|
||||
// Remove fields that should not be in supervisor message
|
||||
delete result.imageList;
|
||||
delete result.metadata;
|
||||
delete result.reasoning;
|
||||
delete result.tools;
|
||||
|
||||
// Add aggregated fields if they exist
|
||||
if (msgPerformance) result.performance = msgPerformance;
|
||||
if (msgUsage) result.usage = msgUsage;
|
||||
|
||||
// Preserve isSupervisor in metadata
|
||||
result.metadata = { isSupervisor: true, ...otherMetadata };
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ const Tool = memo<GroupToolProps>(
|
||||
allowExpand={hasCustomRender}
|
||||
expand={isToolRenderExpand}
|
||||
itemKey={id}
|
||||
onExpandChange={setShowPluginRender}
|
||||
paddingBlock={4}
|
||||
paddingInline={4}
|
||||
title={
|
||||
|
||||
@@ -51,6 +51,8 @@ export default {
|
||||
'builtins.lobe-group-management.apiName.speak': 'Designated member speaks',
|
||||
'builtins.lobe-group-management.apiName.summarize': 'Summarize conversation',
|
||||
'builtins.lobe-group-management.apiName.vote': 'Start vote',
|
||||
'builtins.lobe-group-management.inspector.broadcast.title': 'Following Agents speak:',
|
||||
'builtins.lobe-group-management.inspector.speak.title': 'Designated Agent speaks:',
|
||||
'builtins.lobe-group-management.title': 'Group Coordinator',
|
||||
'builtins.lobe-gtd.apiName.clearTodos': 'Clear todos',
|
||||
'builtins.lobe-gtd.apiName.clearTodos.modeAll': 'all',
|
||||
|
||||
Reference in New Issue
Block a user