mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
🐛 fix(chat): refine workflow collapse headline (#13717)
* 🐛 fix(chat): refine workflow collapse headline * 🐛 fix(chat): use state machine for workflow headline * 🐛 fix(chat): backtrack workflow headline state * ♻️ refactor(chat): simplify workflow headline selector * 💄 style(chat): use lucide workflow collapse arrow * ♻️ refactor(chat): use accordion indicator layout * Move workflow duration text beside the title * Localize workflow tool display labels * Update Page workflow localization labels * fix: sort imports in toolDisplayNames.test.ts
This commit is contained in:
@@ -465,6 +465,80 @@
|
||||
"viewMode.fullWidth": "Full Width",
|
||||
"viewMode.normal": "Standard",
|
||||
"viewMode.wideScreen": "Widescreen",
|
||||
"workflow.failedSuffix": "(failed)",
|
||||
"workflow.thoughtForDuration": "Thought for {{duration}}",
|
||||
"workflow.toolDisplayName.activateDevice": "Activated device",
|
||||
"workflow.toolDisplayName.activateSkill": "Activated a skill",
|
||||
"workflow.toolDisplayName.activateTools": "Activated tools",
|
||||
"workflow.toolDisplayName.addActivityMemory": "Saved memory",
|
||||
"workflow.toolDisplayName.addContextMemory": "Saved memory",
|
||||
"workflow.toolDisplayName.addExperienceMemory": "Saved memory",
|
||||
"workflow.toolDisplayName.addIdentityMemory": "Saved memory",
|
||||
"workflow.toolDisplayName.addPreferenceMemory": "Saved memory",
|
||||
"workflow.toolDisplayName.calculate": "Calculated",
|
||||
"workflow.toolDisplayName.callAgent": "Called an agent",
|
||||
"workflow.toolDisplayName.clearTodos": "Cleared todos",
|
||||
"workflow.toolDisplayName.copyDocument": "Copied a document",
|
||||
"workflow.toolDisplayName.crawlMultiPages": "Crawled pages",
|
||||
"workflow.toolDisplayName.crawlSinglePage": "Crawled a page",
|
||||
"workflow.toolDisplayName.createAgent": "Created an agent",
|
||||
"workflow.toolDisplayName.createDocument": "Created a document",
|
||||
"workflow.toolDisplayName.createPlan": "Created a plan",
|
||||
"workflow.toolDisplayName.createTodos": "Created todos",
|
||||
"workflow.toolDisplayName.deleteAgent": "Deleted an agent",
|
||||
"workflow.toolDisplayName.deleteDocument": "Deleted a document",
|
||||
"workflow.toolDisplayName.editDocument": "Edited a document",
|
||||
"workflow.toolDisplayName.editLocalFile": "Edited a file",
|
||||
"workflow.toolDisplayName.editTitle": "Edited title",
|
||||
"workflow.toolDisplayName.evaluate": "Evaluated expression",
|
||||
"workflow.toolDisplayName.execScript": "Executed a script",
|
||||
"workflow.toolDisplayName.execTask": "Executed a task",
|
||||
"workflow.toolDisplayName.execTasks": "Executed tasks",
|
||||
"workflow.toolDisplayName.execute": "Executed calculation",
|
||||
"workflow.toolDisplayName.executeCode": "Executed code",
|
||||
"workflow.toolDisplayName.finishOnboarding": "Finished onboarding",
|
||||
"workflow.toolDisplayName.getCommandOutput": "Read command output",
|
||||
"workflow.toolDisplayName.getDocument": "Read a document",
|
||||
"workflow.toolDisplayName.getOnboardingState": "Checked onboarding state",
|
||||
"workflow.toolDisplayName.getPageContent": "Read Page content",
|
||||
"workflow.toolDisplayName.getTopicContext": "Read topic context",
|
||||
"workflow.toolDisplayName.globLocalFiles": "Searched files",
|
||||
"workflow.toolDisplayName.grepContent": "Searched content",
|
||||
"workflow.toolDisplayName.importFromMarket": "Imported from market",
|
||||
"workflow.toolDisplayName.importSkill": "Imported a skill",
|
||||
"workflow.toolDisplayName.initPage": "Initialized Page",
|
||||
"workflow.toolDisplayName.killCommand": "Stopped a command",
|
||||
"workflow.toolDisplayName.listDocuments": "Listed documents",
|
||||
"workflow.toolDisplayName.listLocalFiles": "Listed files",
|
||||
"workflow.toolDisplayName.listOnlineDevices": "Listed devices",
|
||||
"workflow.toolDisplayName.modifyNodes": "Modified Page",
|
||||
"workflow.toolDisplayName.moveLocalFiles": "Moved files",
|
||||
"workflow.toolDisplayName.readDocument": "Read a document",
|
||||
"workflow.toolDisplayName.readDocumentByFilename": "Read a document",
|
||||
"workflow.toolDisplayName.readKnowledge": "Read knowledge",
|
||||
"workflow.toolDisplayName.readLocalFile": "Read a file",
|
||||
"workflow.toolDisplayName.removeDocument": "Removed a document",
|
||||
"workflow.toolDisplayName.removeIdentityMemory": "Removed memory",
|
||||
"workflow.toolDisplayName.renameDocument": "Renamed a document",
|
||||
"workflow.toolDisplayName.renameLocalFile": "Renamed a file",
|
||||
"workflow.toolDisplayName.replaceText": "Replaced text",
|
||||
"workflow.toolDisplayName.runCommand": "Ran a command",
|
||||
"workflow.toolDisplayName.search": "Searched the web",
|
||||
"workflow.toolDisplayName.searchAgent": "Searched agents",
|
||||
"workflow.toolDisplayName.searchKnowledgeBase": "Searched knowledge base",
|
||||
"workflow.toolDisplayName.searchLocalFiles": "Searched files",
|
||||
"workflow.toolDisplayName.searchSkill": "Searched skills",
|
||||
"workflow.toolDisplayName.searchUserMemory": "Searched memory",
|
||||
"workflow.toolDisplayName.solve": "Solved equation",
|
||||
"workflow.toolDisplayName.updateAgent": "Updated an agent",
|
||||
"workflow.toolDisplayName.updateDocument": "Updated a document",
|
||||
"workflow.toolDisplayName.updateIdentityMemory": "Updated memory",
|
||||
"workflow.toolDisplayName.updateLoadRule": "Updated load rule",
|
||||
"workflow.toolDisplayName.updatePlan": "Updated plan",
|
||||
"workflow.toolDisplayName.updateTodos": "Updated todos",
|
||||
"workflow.toolDisplayName.upsertDocumentByFilename": "Updated a document",
|
||||
"workflow.toolDisplayName.writeLocalFile": "Wrote a file",
|
||||
"workflow.working": "Working...",
|
||||
"you": "You",
|
||||
"zenMode": "Zen Mode"
|
||||
}
|
||||
|
||||
@@ -465,6 +465,80 @@
|
||||
"viewMode.fullWidth": "全宽显示",
|
||||
"viewMode.normal": "普通",
|
||||
"viewMode.wideScreen": "宽屏",
|
||||
"workflow.failedSuffix": "(失败)",
|
||||
"workflow.thoughtForDuration": "思考了 {{duration}}",
|
||||
"workflow.toolDisplayName.activateDevice": "激活了设备",
|
||||
"workflow.toolDisplayName.activateSkill": "启用了技能",
|
||||
"workflow.toolDisplayName.activateTools": "启用了工具",
|
||||
"workflow.toolDisplayName.addActivityMemory": "保存了记忆",
|
||||
"workflow.toolDisplayName.addContextMemory": "保存了记忆",
|
||||
"workflow.toolDisplayName.addExperienceMemory": "保存了记忆",
|
||||
"workflow.toolDisplayName.addIdentityMemory": "保存了记忆",
|
||||
"workflow.toolDisplayName.addPreferenceMemory": "保存了记忆",
|
||||
"workflow.toolDisplayName.calculate": "完成了计算",
|
||||
"workflow.toolDisplayName.callAgent": "调用了助理",
|
||||
"workflow.toolDisplayName.clearTodos": "清空了待办",
|
||||
"workflow.toolDisplayName.copyDocument": "复制了文档",
|
||||
"workflow.toolDisplayName.crawlMultiPages": "抓取了多个页面",
|
||||
"workflow.toolDisplayName.crawlSinglePage": "抓取了页面",
|
||||
"workflow.toolDisplayName.createAgent": "创建了助理",
|
||||
"workflow.toolDisplayName.createDocument": "创建了文档",
|
||||
"workflow.toolDisplayName.createPlan": "创建了计划",
|
||||
"workflow.toolDisplayName.createTodos": "创建了待办",
|
||||
"workflow.toolDisplayName.deleteAgent": "删除了助理",
|
||||
"workflow.toolDisplayName.deleteDocument": "删除了文档",
|
||||
"workflow.toolDisplayName.editDocument": "编辑了文档",
|
||||
"workflow.toolDisplayName.editLocalFile": "编辑了文件",
|
||||
"workflow.toolDisplayName.editTitle": "编辑了标题",
|
||||
"workflow.toolDisplayName.evaluate": "求值了表达式",
|
||||
"workflow.toolDisplayName.execScript": "执行了脚本",
|
||||
"workflow.toolDisplayName.execTask": "执行了任务",
|
||||
"workflow.toolDisplayName.execTasks": "执行了任务",
|
||||
"workflow.toolDisplayName.execute": "执行了计算",
|
||||
"workflow.toolDisplayName.executeCode": "执行了代码",
|
||||
"workflow.toolDisplayName.finishOnboarding": "完成了引导",
|
||||
"workflow.toolDisplayName.getCommandOutput": "读取了命令输出",
|
||||
"workflow.toolDisplayName.getDocument": "读取了文档",
|
||||
"workflow.toolDisplayName.getOnboardingState": "检查了引导状态",
|
||||
"workflow.toolDisplayName.getPageContent": "读取了文稿内容",
|
||||
"workflow.toolDisplayName.getTopicContext": "读取了话题上下文",
|
||||
"workflow.toolDisplayName.globLocalFiles": "搜索了文件",
|
||||
"workflow.toolDisplayName.grepContent": "搜索了内容",
|
||||
"workflow.toolDisplayName.importFromMarket": "从市场导入了内容",
|
||||
"workflow.toolDisplayName.importSkill": "导入了技能",
|
||||
"workflow.toolDisplayName.initPage": "初始化了文稿",
|
||||
"workflow.toolDisplayName.killCommand": "停止了命令",
|
||||
"workflow.toolDisplayName.listDocuments": "列出了文档",
|
||||
"workflow.toolDisplayName.listLocalFiles": "列出了文件",
|
||||
"workflow.toolDisplayName.listOnlineDevices": "列出了在线设备",
|
||||
"workflow.toolDisplayName.modifyNodes": "修改了文稿",
|
||||
"workflow.toolDisplayName.moveLocalFiles": "移动了文件",
|
||||
"workflow.toolDisplayName.readDocument": "读取了文档",
|
||||
"workflow.toolDisplayName.readDocumentByFilename": "读取了文档",
|
||||
"workflow.toolDisplayName.readKnowledge": "读取了知识",
|
||||
"workflow.toolDisplayName.readLocalFile": "读取了文件",
|
||||
"workflow.toolDisplayName.removeDocument": "移除了文档",
|
||||
"workflow.toolDisplayName.removeIdentityMemory": "移除了记忆",
|
||||
"workflow.toolDisplayName.renameDocument": "重命名了文档",
|
||||
"workflow.toolDisplayName.renameLocalFile": "重命名了文件",
|
||||
"workflow.toolDisplayName.replaceText": "替换了文本",
|
||||
"workflow.toolDisplayName.runCommand": "运行了命令",
|
||||
"workflow.toolDisplayName.search": "搜索了网络",
|
||||
"workflow.toolDisplayName.searchAgent": "搜索了助理",
|
||||
"workflow.toolDisplayName.searchKnowledgeBase": "搜索了知识库",
|
||||
"workflow.toolDisplayName.searchLocalFiles": "搜索了文件",
|
||||
"workflow.toolDisplayName.searchSkill": "搜索了技能",
|
||||
"workflow.toolDisplayName.searchUserMemory": "搜索了记忆",
|
||||
"workflow.toolDisplayName.solve": "求解了方程",
|
||||
"workflow.toolDisplayName.updateAgent": "更新了助理",
|
||||
"workflow.toolDisplayName.updateDocument": "更新了文档",
|
||||
"workflow.toolDisplayName.updateIdentityMemory": "更新了记忆",
|
||||
"workflow.toolDisplayName.updateLoadRule": "更新了加载规则",
|
||||
"workflow.toolDisplayName.updatePlan": "更新了计划",
|
||||
"workflow.toolDisplayName.updateTodos": "更新了待办",
|
||||
"workflow.toolDisplayName.upsertDocumentByFilename": "更新了文档",
|
||||
"workflow.toolDisplayName.writeLocalFile": "写入了文件",
|
||||
"workflow.working": "处理中...",
|
||||
"you": "你",
|
||||
"zenMode": "专注模式"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { cssVar } from 'antd-style';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { AnimatePresence, m as motion } from 'motion/react';
|
||||
import { type Key, memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
|
||||
import { useAutoScroll } from '@/hooks/useAutoScroll';
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
import {
|
||||
areWorkflowToolsComplete,
|
||||
formatReasoningDuration,
|
||||
getWorkflowStreamingHeadlineParts,
|
||||
getWorkflowStreamingHeadlineState,
|
||||
getWorkflowSummaryText,
|
||||
hasToolError,
|
||||
shapeProseForWorkflowHeadline,
|
||||
@@ -42,7 +43,7 @@ const collectTools = (blocks: AssistantContentBlock[]): ChatToolPayloadWithResul
|
||||
return blocks.flatMap((b) => b.tools ?? []);
|
||||
};
|
||||
|
||||
const useDebouncedHeadline = (raw: string, allComplete: boolean) => {
|
||||
const useDebouncedHeadline = (raw: string, allComplete: boolean, immediate = false) => {
|
||||
const [out, setOut] = useState(raw);
|
||||
const prevCompleteRef = useRef(allComplete);
|
||||
|
||||
@@ -51,6 +52,10 @@ const useDebouncedHeadline = (raw: string, allComplete: boolean) => {
|
||||
prevCompleteRef.current = allComplete;
|
||||
const streaming = !allComplete;
|
||||
|
||||
if (immediate) {
|
||||
setOut(raw);
|
||||
return;
|
||||
}
|
||||
if (!streaming) {
|
||||
setOut(raw);
|
||||
return;
|
||||
@@ -61,7 +66,7 @@ const useDebouncedHeadline = (raw: string, allComplete: boolean) => {
|
||||
}
|
||||
const id = window.setTimeout(() => setOut(raw), WORKFLOW_HEADLINE_DEBOUNCE_MS);
|
||||
return () => window.clearTimeout(id);
|
||||
}, [allComplete, raw]);
|
||||
}, [allComplete, immediate, raw]);
|
||||
|
||||
return !allComplete ? out : raw;
|
||||
};
|
||||
@@ -94,6 +99,7 @@ const useCommittedProseHeadline = (proseSource: string, streaming: boolean) => {
|
||||
|
||||
const WorkflowCollapse = memo<WorkflowCollapseProps>(
|
||||
({ assistantMessageId, blocks, disableEditing, workflowChromeComplete = false }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const allTools = useMemo(() => collectTools(blocks), [blocks]);
|
||||
const toolsPhaseComplete = areWorkflowToolsComplete(allTools);
|
||||
const isGenerating = useConversationStore(
|
||||
@@ -131,19 +137,36 @@ const WorkflowCollapse = memo<WorkflowCollapseProps>(
|
||||
const streaming = !allComplete;
|
||||
const isExpanded = expanded;
|
||||
|
||||
const { explicitStep, fallbackTool, proseSource } = useMemo(
|
||||
() => getWorkflowStreamingHeadlineParts(blocks, allTools),
|
||||
[blocks, allTools],
|
||||
const headlineState = useMemo(() => getWorkflowStreamingHeadlineState(blocks), [blocks]);
|
||||
const committedProse = useCommittedProseHeadline(
|
||||
headlineState.kind === 'prose' ? headlineState.proseSource : '',
|
||||
streaming,
|
||||
);
|
||||
const committedProse = useCommittedProseHeadline(proseSource, streaming);
|
||||
|
||||
const showExpandedWorkingLabel = streaming && isExpanded;
|
||||
const workingLabel = t('workflow.working', { defaultValue: 'Working...' });
|
||||
const streamingHeadlineRaw = useMemo(() => {
|
||||
if (explicitStep) return explicitStep;
|
||||
if (committedProse) return committedProse;
|
||||
if (fallbackTool) return fallbackTool;
|
||||
return '';
|
||||
}, [committedProse, explicitStep, fallbackTool]);
|
||||
const streamingHeadline = useDebouncedHeadline(streamingHeadlineRaw, allComplete);
|
||||
if (showExpandedWorkingLabel) return workingLabel;
|
||||
switch (headlineState.kind) {
|
||||
case 'thinking': {
|
||||
return headlineState.reasoningTitle;
|
||||
}
|
||||
case 'tool': {
|
||||
return headlineState.explicitStep || headlineState.fallbackTool;
|
||||
}
|
||||
case 'prose': {
|
||||
return committedProse;
|
||||
}
|
||||
default: {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}, [committedProse, headlineState, showExpandedWorkingLabel, workingLabel]);
|
||||
const streamingHeadline = useDebouncedHeadline(
|
||||
streamingHeadlineRaw,
|
||||
allComplete,
|
||||
showExpandedWorkingLabel,
|
||||
);
|
||||
|
||||
const [workingElapsedSeconds, setWorkingElapsedSeconds] = useState(0);
|
||||
|
||||
@@ -210,7 +233,7 @@ const WorkflowCollapse = memo<WorkflowCollapseProps>(
|
||||
gap={6}
|
||||
style={{ minHeight: WORKFLOW_STREAMING_TITLE_MIN_HEIGHT_PX, minWidth: 0 }}
|
||||
>
|
||||
<div style={{ flex: 1, minWidth: 0, overflow: 'hidden' }}>
|
||||
<div style={{ minWidth: 0, overflow: 'hidden' }}>
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
@@ -232,7 +255,7 @@ const WorkflowCollapse = memo<WorkflowCollapseProps>(
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{streamingHeadline || 'Working...'}
|
||||
{streamingHeadline || workingLabel}
|
||||
</span>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
@@ -246,8 +269,13 @@ const WorkflowCollapse = memo<WorkflowCollapseProps>(
|
||||
) : (
|
||||
<Flexbox horizontal align="center" gap={6} style={{ minWidth: 0, overflow: 'hidden' }}>
|
||||
<Text
|
||||
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
||||
type="secondary"
|
||||
style={{
|
||||
minWidth: 0,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{summaryText}
|
||||
</Text>
|
||||
@@ -264,7 +292,6 @@ const WorkflowCollapse = memo<WorkflowCollapseProps>(
|
||||
return (
|
||||
<Accordion
|
||||
expandedKeys={isExpanded ? ['workflow'] : []}
|
||||
indicatorPlacement="end"
|
||||
variant="borderless"
|
||||
onExpandedChange={handleExpandedChange}
|
||||
>
|
||||
|
||||
@@ -102,106 +102,106 @@ export const DURATION_SECONDS_PER_MINUTE = 60;
|
||||
/** Duration inputs are in milliseconds; convert to whole seconds for display. */
|
||||
export const TIME_MS_PER_SECOND = 1000;
|
||||
|
||||
// ─── apiName → past-tense human-readable label (workflow summary & headlines) ─
|
||||
// ─── apiName → i18n key for human-readable label (workflow summary & headlines) ─
|
||||
|
||||
/** Past-tense labels for built-in / known tool api names. Unknown api names use title-cased fallback. */
|
||||
/** Translation keys for built-in / known tool api names. Unknown api names use title-cased fallback. */
|
||||
export const TOOL_API_DISPLAY_NAMES: Record<string, string> = {
|
||||
// Web browsing
|
||||
crawlMultiPages: 'Crawled pages',
|
||||
crawlSinglePage: 'Crawled a page',
|
||||
search: 'Searched the web',
|
||||
crawlMultiPages: 'workflow.toolDisplayName.crawlMultiPages',
|
||||
crawlSinglePage: 'workflow.toolDisplayName.crawlSinglePage',
|
||||
search: 'workflow.toolDisplayName.search',
|
||||
|
||||
// Knowledge base
|
||||
readKnowledge: 'Read knowledge',
|
||||
searchKnowledgeBase: 'Searched knowledge base',
|
||||
readKnowledge: 'workflow.toolDisplayName.readKnowledge',
|
||||
searchKnowledgeBase: 'workflow.toolDisplayName.searchKnowledgeBase',
|
||||
|
||||
// Notebook
|
||||
createDocument: 'Created a document',
|
||||
deleteDocument: 'Deleted a document',
|
||||
getDocument: 'Read a document',
|
||||
updateDocument: 'Updated a document',
|
||||
createDocument: 'workflow.toolDisplayName.createDocument',
|
||||
deleteDocument: 'workflow.toolDisplayName.deleteDocument',
|
||||
getDocument: 'workflow.toolDisplayName.getDocument',
|
||||
updateDocument: 'workflow.toolDisplayName.updateDocument',
|
||||
|
||||
// Agent documents
|
||||
copyDocument: 'Copied a document',
|
||||
editDocument: 'Edited a document',
|
||||
listDocuments: 'Listed documents',
|
||||
readDocument: 'Read a document',
|
||||
readDocumentByFilename: 'Read a document',
|
||||
removeDocument: 'Removed a document',
|
||||
renameDocument: 'Renamed a document',
|
||||
upsertDocumentByFilename: 'Updated a document',
|
||||
updateLoadRule: 'Updated load rule',
|
||||
copyDocument: 'workflow.toolDisplayName.copyDocument',
|
||||
editDocument: 'workflow.toolDisplayName.editDocument',
|
||||
listDocuments: 'workflow.toolDisplayName.listDocuments',
|
||||
readDocument: 'workflow.toolDisplayName.readDocument',
|
||||
readDocumentByFilename: 'workflow.toolDisplayName.readDocumentByFilename',
|
||||
removeDocument: 'workflow.toolDisplayName.removeDocument',
|
||||
renameDocument: 'workflow.toolDisplayName.renameDocument',
|
||||
upsertDocumentByFilename: 'workflow.toolDisplayName.upsertDocumentByFilename',
|
||||
updateLoadRule: 'workflow.toolDisplayName.updateLoadRule',
|
||||
|
||||
// Calculator
|
||||
calculate: 'Calculated',
|
||||
evaluate: 'Evaluated expression',
|
||||
solve: 'Solved equation',
|
||||
execute: 'Executed calculation',
|
||||
calculate: 'workflow.toolDisplayName.calculate',
|
||||
evaluate: 'workflow.toolDisplayName.evaluate',
|
||||
solve: 'workflow.toolDisplayName.solve',
|
||||
execute: 'workflow.toolDisplayName.execute',
|
||||
|
||||
// Local system
|
||||
editLocalFile: 'Edited a file',
|
||||
globLocalFiles: 'Searched files',
|
||||
grepContent: 'Searched content',
|
||||
killCommand: 'Stopped a command',
|
||||
listLocalFiles: 'Listed files',
|
||||
moveLocalFiles: 'Moved files',
|
||||
readLocalFile: 'Read a file',
|
||||
renameLocalFile: 'Renamed a file',
|
||||
runCommand: 'Ran a command',
|
||||
searchLocalFiles: 'Searched files',
|
||||
writeLocalFile: 'Wrote a file',
|
||||
getCommandOutput: 'Read command output',
|
||||
editLocalFile: 'workflow.toolDisplayName.editLocalFile',
|
||||
globLocalFiles: 'workflow.toolDisplayName.globLocalFiles',
|
||||
grepContent: 'workflow.toolDisplayName.grepContent',
|
||||
killCommand: 'workflow.toolDisplayName.killCommand',
|
||||
listLocalFiles: 'workflow.toolDisplayName.listLocalFiles',
|
||||
moveLocalFiles: 'workflow.toolDisplayName.moveLocalFiles',
|
||||
readLocalFile: 'workflow.toolDisplayName.readLocalFile',
|
||||
renameLocalFile: 'workflow.toolDisplayName.renameLocalFile',
|
||||
runCommand: 'workflow.toolDisplayName.runCommand',
|
||||
searchLocalFiles: 'workflow.toolDisplayName.searchLocalFiles',
|
||||
writeLocalFile: 'workflow.toolDisplayName.writeLocalFile',
|
||||
getCommandOutput: 'workflow.toolDisplayName.getCommandOutput',
|
||||
|
||||
// Cloud sandbox
|
||||
executeCode: 'Executed code',
|
||||
executeCode: 'workflow.toolDisplayName.executeCode',
|
||||
|
||||
// GTD
|
||||
createPlan: 'Created a plan',
|
||||
createTodos: 'Created todos',
|
||||
updatePlan: 'Updated plan',
|
||||
updateTodos: 'Updated todos',
|
||||
clearTodos: 'Cleared todos',
|
||||
execTask: 'Executed a task',
|
||||
execTasks: 'Executed tasks',
|
||||
createPlan: 'workflow.toolDisplayName.createPlan',
|
||||
createTodos: 'workflow.toolDisplayName.createTodos',
|
||||
updatePlan: 'workflow.toolDisplayName.updatePlan',
|
||||
updateTodos: 'workflow.toolDisplayName.updateTodos',
|
||||
clearTodos: 'workflow.toolDisplayName.clearTodos',
|
||||
execTask: 'workflow.toolDisplayName.execTask',
|
||||
execTasks: 'workflow.toolDisplayName.execTasks',
|
||||
|
||||
// Memory
|
||||
addActivityMemory: 'Saved memory',
|
||||
addContextMemory: 'Saved memory',
|
||||
addExperienceMemory: 'Saved memory',
|
||||
addIdentityMemory: 'Saved memory',
|
||||
addPreferenceMemory: 'Saved memory',
|
||||
removeIdentityMemory: 'Removed memory',
|
||||
searchUserMemory: 'Searched memory',
|
||||
updateIdentityMemory: 'Updated memory',
|
||||
addActivityMemory: 'workflow.toolDisplayName.addActivityMemory',
|
||||
addContextMemory: 'workflow.toolDisplayName.addContextMemory',
|
||||
addExperienceMemory: 'workflow.toolDisplayName.addExperienceMemory',
|
||||
addIdentityMemory: 'workflow.toolDisplayName.addIdentityMemory',
|
||||
addPreferenceMemory: 'workflow.toolDisplayName.addPreferenceMemory',
|
||||
removeIdentityMemory: 'workflow.toolDisplayName.removeIdentityMemory',
|
||||
searchUserMemory: 'workflow.toolDisplayName.searchUserMemory',
|
||||
updateIdentityMemory: 'workflow.toolDisplayName.updateIdentityMemory',
|
||||
|
||||
// Agent management
|
||||
callAgent: 'Called an agent',
|
||||
createAgent: 'Created an agent',
|
||||
deleteAgent: 'Deleted an agent',
|
||||
searchAgent: 'Searched agents',
|
||||
updateAgent: 'Updated an agent',
|
||||
callAgent: 'workflow.toolDisplayName.callAgent',
|
||||
createAgent: 'workflow.toolDisplayName.createAgent',
|
||||
deleteAgent: 'workflow.toolDisplayName.deleteAgent',
|
||||
searchAgent: 'workflow.toolDisplayName.searchAgent',
|
||||
updateAgent: 'workflow.toolDisplayName.updateAgent',
|
||||
|
||||
// Page agent
|
||||
editTitle: 'Edited title',
|
||||
getPageContent: 'Read page content',
|
||||
initPage: 'Initialized page',
|
||||
modifyNodes: 'Modified page',
|
||||
replaceText: 'Replaced text',
|
||||
editTitle: 'workflow.toolDisplayName.editTitle',
|
||||
getPageContent: 'workflow.toolDisplayName.getPageContent',
|
||||
initPage: 'workflow.toolDisplayName.initPage',
|
||||
modifyNodes: 'workflow.toolDisplayName.modifyNodes',
|
||||
replaceText: 'workflow.toolDisplayName.replaceText',
|
||||
|
||||
// Skills
|
||||
activateSkill: 'Activated a skill',
|
||||
activateTools: 'Activated tools',
|
||||
execScript: 'Executed a script',
|
||||
activateSkill: 'workflow.toolDisplayName.activateSkill',
|
||||
activateTools: 'workflow.toolDisplayName.activateTools',
|
||||
execScript: 'workflow.toolDisplayName.execScript',
|
||||
|
||||
// Skill store
|
||||
importFromMarket: 'Imported from market',
|
||||
importSkill: 'Imported a skill',
|
||||
searchSkill: 'Searched skills',
|
||||
importFromMarket: 'workflow.toolDisplayName.importFromMarket',
|
||||
importSkill: 'workflow.toolDisplayName.importSkill',
|
||||
searchSkill: 'workflow.toolDisplayName.searchSkill',
|
||||
|
||||
// Misc
|
||||
finishOnboarding: 'Finished onboarding',
|
||||
getOnboardingState: 'Checked onboarding state',
|
||||
getTopicContext: 'Read topic context',
|
||||
listOnlineDevices: 'Listed devices',
|
||||
activateDevice: 'Activated device',
|
||||
finishOnboarding: 'workflow.toolDisplayName.finishOnboarding',
|
||||
getOnboardingState: 'workflow.toolDisplayName.getOnboardingState',
|
||||
getTopicContext: 'workflow.toolDisplayName.getTopicContext',
|
||||
listOnlineDevices: 'workflow.toolDisplayName.listOnlineDevices',
|
||||
activateDevice: 'workflow.toolDisplayName.activateDevice',
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { type AssistantContentBlock } from '@/types/index';
|
||||
import { POST_TOOL_FINAL_ANSWER_SCORE_THRESHOLD } from './constants';
|
||||
import {
|
||||
getPostToolAnswerSplitIndex,
|
||||
getWorkflowStreamingHeadlineState,
|
||||
scorePostToolBlockAsFinalAnswer,
|
||||
shapeProseForWorkflowHeadline,
|
||||
} from './toolDisplayNames';
|
||||
@@ -54,3 +55,122 @@ describe('post-tool final answer split', () => {
|
||||
expect(getPostToolAnswerSplitIndex(blocks, 0, true, true)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('reasoning headline extraction', () => {
|
||||
it('uses the last markdown heading for a trailing thinking-only block', () => {
|
||||
const state = getWorkflowStreamingHeadlineState([
|
||||
blk({
|
||||
id: '0',
|
||||
content: '',
|
||||
reasoning: {
|
||||
content:
|
||||
'# Initial framing\n\nSome details.\n\n## Search release notes\n\nMore details.\n\n### Finalize patch plan',
|
||||
} as any,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(state).toEqual({
|
||||
kind: 'thinking',
|
||||
reasoningTitle: 'Finalize patch plan',
|
||||
});
|
||||
});
|
||||
|
||||
it('prefers tool state when the trailing block has tools', () => {
|
||||
const state = getWorkflowStreamingHeadlineState([
|
||||
blk({
|
||||
id: '0',
|
||||
reasoning: {
|
||||
content: '### Search release notes',
|
||||
} as any,
|
||||
}),
|
||||
blk({
|
||||
id: '1',
|
||||
tools: [
|
||||
{
|
||||
apiName: 'search',
|
||||
arguments: '{"query":"Node.js 24"}',
|
||||
result: {
|
||||
state: { workflowHeadline: { stepMessage: 'Searching release notes' } },
|
||||
},
|
||||
} as any,
|
||||
],
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(state).toEqual({
|
||||
explicitStep: 'Searched the web: Searching release notes',
|
||||
fallbackTool: 'Searched the web: Node.js 24',
|
||||
kind: 'tool',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses prose state when the trailing block is prose', () => {
|
||||
const state = getWorkflowStreamingHeadlineState([
|
||||
blk({
|
||||
id: '0',
|
||||
tools: [{ apiName: 'search', id: 't1' } as any],
|
||||
}),
|
||||
blk({
|
||||
id: '1',
|
||||
content: 'Now I will compare the release notes and summarize the migration changes.',
|
||||
reasoning: {
|
||||
content: '### Planning',
|
||||
} as any,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(state).toEqual({
|
||||
kind: 'prose',
|
||||
proseSource: 'Now I will compare the release notes and summarize the migration changes.',
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to the previous usable block when trailing thinking has no heading', () => {
|
||||
const state = getWorkflowStreamingHeadlineState([
|
||||
blk({
|
||||
id: '0',
|
||||
tools: [
|
||||
{
|
||||
apiName: 'search',
|
||||
arguments: '{"query":"Node.js 24"}',
|
||||
result: {
|
||||
state: { workflowHeadline: { stepMessage: 'Searching release notes' } },
|
||||
},
|
||||
} as any,
|
||||
],
|
||||
}),
|
||||
blk({
|
||||
id: '1',
|
||||
reasoning: {
|
||||
content: 'Thinking through the comparison strategy without a markdown heading.',
|
||||
} as any,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(state).toEqual({
|
||||
explicitStep: 'Searched the web: Searching release notes',
|
||||
fallbackTool: 'Searched the web: Node.js 24',
|
||||
kind: 'tool',
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to the previous usable block when trailing prose is too short', () => {
|
||||
const state = getWorkflowStreamingHeadlineState([
|
||||
blk({
|
||||
id: '0',
|
||||
reasoning: {
|
||||
content: '### Search release notes',
|
||||
} as any,
|
||||
}),
|
||||
blk({
|
||||
id: '1',
|
||||
content: 'ok',
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(state).toEqual({
|
||||
kind: 'thinking',
|
||||
reasoningTitle: 'Search release notes',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type ChatToolPayloadWithResult } from '@lobechat/types';
|
||||
import { t } from 'i18next';
|
||||
|
||||
import { LOADING_FLAT } from '@/const/message';
|
||||
import { type AssistantContentBlock } from '@/types/index';
|
||||
@@ -95,7 +96,11 @@ const toTitleCase = (apiName: string): string => {
|
||||
};
|
||||
|
||||
export const getToolDisplayName = (apiName: string): string => {
|
||||
return TOOL_API_DISPLAY_NAMES[apiName] || toTitleCase(apiName);
|
||||
const defaultValue = toTitleCase(apiName);
|
||||
const key = TOOL_API_DISPLAY_NAMES[apiName];
|
||||
if (!key) return defaultValue;
|
||||
|
||||
return t(key, { defaultValue, ns: 'chat' });
|
||||
};
|
||||
|
||||
export const getToolSummaryText = (tools: ChatToolPayloadWithResult[]): string => {
|
||||
@@ -235,6 +240,27 @@ const stripLightMarkdownForHeadline = (md: string): string => {
|
||||
return s;
|
||||
};
|
||||
|
||||
const extractMarkdownHeadingTitle = (md: string): string => {
|
||||
const withoutCode = md.replaceAll(/```[\s\S]*?```/g, ' ');
|
||||
const lines = withoutCode.split('\n');
|
||||
let lastTitle = '';
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(
|
||||
new RegExp(`^\\s{0,3}#{1,${WORKFLOW_MARKDOWN_HEADING_MAX_LEVEL}}\\s+(.+?)\\s*$`),
|
||||
);
|
||||
if (!match) continue;
|
||||
|
||||
const raw = match[1]?.replace(/\s+#+\s*$/, '') ?? '';
|
||||
const title = stripLightMarkdownForHeadline(raw).replaceAll(/\s+/g, ' ').trim();
|
||||
if (!title) continue;
|
||||
|
||||
lastTitle = truncateDisplayAtWord(title, WORKFLOW_PROSE_HEADLINE_MAX_CHARS);
|
||||
}
|
||||
|
||||
return lastTitle;
|
||||
};
|
||||
|
||||
/**
|
||||
* Deterministic one-line snippet from streamed assistant prose (A path).
|
||||
* Prefers a full sentence when punctuation exists; otherwise trims to max width.
|
||||
@@ -256,34 +282,75 @@ export const shapeProseForWorkflowHeadline = (source: string): string => {
|
||||
return truncateDisplayAtWord(s, WORKFLOW_PROSE_HEADLINE_MAX_CHARS);
|
||||
};
|
||||
|
||||
/** Raw assistant `content` from the latest block that qualifies (scan from end). */
|
||||
export const extractLatestProseHeadlineSource = (blocks: AssistantContentBlock[]): string => {
|
||||
for (let i = blocks.length - 1; i >= 0; i--) {
|
||||
const c = blocks[i]?.content?.trim() ?? '';
|
||||
if (!c || c === LOADING_FLAT) continue;
|
||||
if (c.length < WORKFLOW_PROSE_SOURCE_MIN_CHARS) continue;
|
||||
return c;
|
||||
}
|
||||
return '';
|
||||
const getBlockContent = (block: AssistantContentBlock): string => {
|
||||
const content = block.content?.trim() ?? '';
|
||||
if (!content || content === LOADING_FLAT) return '';
|
||||
return content;
|
||||
};
|
||||
|
||||
export interface WorkflowStreamingHeadlineParts {
|
||||
explicitStep: string;
|
||||
fallbackTool: string;
|
||||
proseSource: string;
|
||||
}
|
||||
const getBlockReasoningContent = (block: AssistantContentBlock): string => {
|
||||
const reasoning = block.reasoning?.content?.trim() ?? '';
|
||||
if (!reasoning || reasoning === LOADING_FLAT) return '';
|
||||
return reasoning;
|
||||
};
|
||||
|
||||
/** Split B / raw A source / C for streaming headline composition (A commits in UI with idle/sentence rules). */
|
||||
export const getWorkflowStreamingHeadlineParts = (
|
||||
const isThinkingOnlyBlock = (block: AssistantContentBlock): boolean => {
|
||||
if (block.tools?.length) return false;
|
||||
if ((block.imageList?.length ?? 0) > 0) return false;
|
||||
return !!getBlockReasoningContent(block) && !getBlockContent(block) && !block.error;
|
||||
};
|
||||
|
||||
export type WorkflowStreamingHeadlineState =
|
||||
| { kind: 'idle' }
|
||||
| { kind: 'prose'; proseSource: string }
|
||||
| { kind: 'thinking'; reasoningTitle: string }
|
||||
| { explicitStep: string; fallbackTool: string; kind: 'tool' };
|
||||
|
||||
const getHeadlineStateFromBlock = (
|
||||
block: AssistantContentBlock,
|
||||
): WorkflowStreamingHeadlineState | null => {
|
||||
if (block.tools?.length) {
|
||||
const lastTool = block.tools.at(-1);
|
||||
const explicitStep = lastTool ? getExplicitStepHeadlineLine(lastTool) : '';
|
||||
const fallbackTool = lastTool ? getToolFallbackHeadlineLine(lastTool) : '';
|
||||
if (!explicitStep && !fallbackTool) return null;
|
||||
|
||||
return {
|
||||
explicitStep,
|
||||
fallbackTool,
|
||||
kind: 'tool',
|
||||
};
|
||||
}
|
||||
|
||||
if (isThinkingOnlyBlock(block)) {
|
||||
const reasoningTitle = extractMarkdownHeadingTitle(getBlockReasoningContent(block));
|
||||
if (!reasoningTitle) return null;
|
||||
|
||||
return {
|
||||
kind: 'thinking',
|
||||
reasoningTitle,
|
||||
};
|
||||
}
|
||||
|
||||
const proseSource = getBlockContent(block);
|
||||
if (proseSource.length < WORKFLOW_PROSE_SOURCE_MIN_CHARS) return null;
|
||||
|
||||
return { kind: 'prose', proseSource };
|
||||
};
|
||||
|
||||
/** Walk backward and return the first block that can produce a meaningful headline state. */
|
||||
export const getWorkflowStreamingHeadlineState = (
|
||||
blocks: AssistantContentBlock[],
|
||||
tools: ChatToolPayloadWithResult[],
|
||||
): WorkflowStreamingHeadlineParts => {
|
||||
const last = tools.at(-1);
|
||||
return {
|
||||
explicitStep: last ? getExplicitStepHeadlineLine(last) : '',
|
||||
fallbackTool: last ? getToolFallbackHeadlineLine(last) : '',
|
||||
proseSource: extractLatestProseHeadlineSource(blocks),
|
||||
};
|
||||
): WorkflowStreamingHeadlineState => {
|
||||
for (let i = blocks.length - 1; i >= 0; i--) {
|
||||
const block = blocks[i];
|
||||
if (!block) continue;
|
||||
|
||||
const state = getHeadlineStateFromBlock(block);
|
||||
if (state) return state;
|
||||
}
|
||||
|
||||
return { kind: 'idle' };
|
||||
};
|
||||
|
||||
export const formatReasoningDuration = (ms: number): string => {
|
||||
@@ -309,7 +376,8 @@ export const getWorkflowSummaryText = (blocks: AssistantContentBlock[]): string
|
||||
for (const [apiName, { count, errorCount }] of groups) {
|
||||
let part = getToolDisplayName(apiName);
|
||||
if (count > 1) part += ` (${count})`;
|
||||
if (errorCount > 0) part += ' (failed)';
|
||||
if (errorCount > 0)
|
||||
part += ` ${t('workflow.failedSuffix', { defaultValue: '(failed)', ns: 'chat' })}`;
|
||||
toolParts.push(part);
|
||||
}
|
||||
|
||||
@@ -317,7 +385,11 @@ export const getWorkflowSummaryText = (blocks: AssistantContentBlock[]): string
|
||||
|
||||
const totalReasoningMs = blocks.reduce((sum, b) => sum + (b.reasoning?.duration ?? 0), 0);
|
||||
if (totalReasoningMs > 0) {
|
||||
result += ` · Thought for ${formatReasoningDuration(totalReasoningMs)}`;
|
||||
result += ` · ${t('workflow.thoughtForDuration', {
|
||||
defaultValue: 'Thought for {{duration}}',
|
||||
duration: formatReasoningDuration(totalReasoningMs),
|
||||
ns: 'chat',
|
||||
})}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -507,6 +507,80 @@ export default {
|
||||
'viewMode.fullWidth': 'Full Width',
|
||||
'viewMode.normal': 'Standard',
|
||||
'viewMode.wideScreen': 'Widescreen',
|
||||
'workflow.failedSuffix': '(failed)',
|
||||
'workflow.thoughtForDuration': 'Thought for {{duration}}',
|
||||
'workflow.toolDisplayName.activateDevice': 'Activated device',
|
||||
'workflow.toolDisplayName.activateSkill': 'Activated a skill',
|
||||
'workflow.toolDisplayName.activateTools': 'Activated tools',
|
||||
'workflow.toolDisplayName.addActivityMemory': 'Saved memory',
|
||||
'workflow.toolDisplayName.addContextMemory': 'Saved memory',
|
||||
'workflow.toolDisplayName.addExperienceMemory': 'Saved memory',
|
||||
'workflow.toolDisplayName.addIdentityMemory': 'Saved memory',
|
||||
'workflow.toolDisplayName.addPreferenceMemory': 'Saved memory',
|
||||
'workflow.toolDisplayName.calculate': 'Calculated',
|
||||
'workflow.toolDisplayName.callAgent': 'Called an agent',
|
||||
'workflow.toolDisplayName.clearTodos': 'Cleared todos',
|
||||
'workflow.toolDisplayName.copyDocument': 'Copied a document',
|
||||
'workflow.toolDisplayName.crawlMultiPages': 'Crawled pages',
|
||||
'workflow.toolDisplayName.crawlSinglePage': 'Crawled a page',
|
||||
'workflow.toolDisplayName.createAgent': 'Created an agent',
|
||||
'workflow.toolDisplayName.createDocument': 'Created a document',
|
||||
'workflow.toolDisplayName.createPlan': 'Created a plan',
|
||||
'workflow.toolDisplayName.createTodos': 'Created todos',
|
||||
'workflow.toolDisplayName.deleteAgent': 'Deleted an agent',
|
||||
'workflow.toolDisplayName.deleteDocument': 'Deleted a document',
|
||||
'workflow.toolDisplayName.editDocument': 'Edited a document',
|
||||
'workflow.toolDisplayName.editLocalFile': 'Edited a file',
|
||||
'workflow.toolDisplayName.editTitle': 'Edited title',
|
||||
'workflow.toolDisplayName.evaluate': 'Evaluated expression',
|
||||
'workflow.toolDisplayName.execScript': 'Executed a script',
|
||||
'workflow.toolDisplayName.execTask': 'Executed a task',
|
||||
'workflow.toolDisplayName.execTasks': 'Executed tasks',
|
||||
'workflow.toolDisplayName.execute': 'Executed calculation',
|
||||
'workflow.toolDisplayName.executeCode': 'Executed code',
|
||||
'workflow.toolDisplayName.finishOnboarding': 'Finished onboarding',
|
||||
'workflow.toolDisplayName.getCommandOutput': 'Read command output',
|
||||
'workflow.toolDisplayName.getDocument': 'Read a document',
|
||||
'workflow.toolDisplayName.getOnboardingState': 'Checked onboarding state',
|
||||
'workflow.toolDisplayName.getPageContent': 'Read Page content',
|
||||
'workflow.toolDisplayName.getTopicContext': 'Read topic context',
|
||||
'workflow.toolDisplayName.globLocalFiles': 'Searched files',
|
||||
'workflow.toolDisplayName.grepContent': 'Searched content',
|
||||
'workflow.toolDisplayName.importFromMarket': 'Imported from market',
|
||||
'workflow.toolDisplayName.importSkill': 'Imported a skill',
|
||||
'workflow.toolDisplayName.initPage': 'Initialized Page',
|
||||
'workflow.toolDisplayName.killCommand': 'Stopped a command',
|
||||
'workflow.toolDisplayName.listDocuments': 'Listed documents',
|
||||
'workflow.toolDisplayName.listLocalFiles': 'Listed files',
|
||||
'workflow.toolDisplayName.listOnlineDevices': 'Listed devices',
|
||||
'workflow.toolDisplayName.modifyNodes': 'Modified Page',
|
||||
'workflow.toolDisplayName.moveLocalFiles': 'Moved files',
|
||||
'workflow.toolDisplayName.readDocument': 'Read a document',
|
||||
'workflow.toolDisplayName.readDocumentByFilename': 'Read a document',
|
||||
'workflow.toolDisplayName.readKnowledge': 'Read knowledge',
|
||||
'workflow.toolDisplayName.readLocalFile': 'Read a file',
|
||||
'workflow.toolDisplayName.removeDocument': 'Removed a document',
|
||||
'workflow.toolDisplayName.removeIdentityMemory': 'Removed memory',
|
||||
'workflow.toolDisplayName.renameDocument': 'Renamed a document',
|
||||
'workflow.toolDisplayName.renameLocalFile': 'Renamed a file',
|
||||
'workflow.toolDisplayName.replaceText': 'Replaced text',
|
||||
'workflow.toolDisplayName.runCommand': 'Ran a command',
|
||||
'workflow.toolDisplayName.search': 'Searched the web',
|
||||
'workflow.toolDisplayName.searchAgent': 'Searched agents',
|
||||
'workflow.toolDisplayName.searchKnowledgeBase': 'Searched knowledge base',
|
||||
'workflow.toolDisplayName.searchLocalFiles': 'Searched files',
|
||||
'workflow.toolDisplayName.searchSkill': 'Searched skills',
|
||||
'workflow.toolDisplayName.searchUserMemory': 'Searched memory',
|
||||
'workflow.toolDisplayName.solve': 'Solved equation',
|
||||
'workflow.toolDisplayName.updateAgent': 'Updated an agent',
|
||||
'workflow.toolDisplayName.updateDocument': 'Updated a document',
|
||||
'workflow.toolDisplayName.updateIdentityMemory': 'Updated memory',
|
||||
'workflow.toolDisplayName.updateLoadRule': 'Updated load rule',
|
||||
'workflow.toolDisplayName.updatePlan': 'Updated plan',
|
||||
'workflow.toolDisplayName.updateTodos': 'Updated todos',
|
||||
'workflow.toolDisplayName.upsertDocumentByFilename': 'Updated a document',
|
||||
'workflow.toolDisplayName.writeLocalFile': 'Wrote a file',
|
||||
'workflow.working': 'Working...',
|
||||
'you': 'You',
|
||||
'zenMode': 'Zen Mode',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user