mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
💄 style: support tool streaming and title custom render (#10976)
* support custom inspector * support local-system inspector * add streaming feature * merge
This commit is contained in:
@@ -16,6 +16,21 @@
|
||||
"builtins.lobe-agent-builder.apiName.updateMeta": "更新元数据",
|
||||
"builtins.lobe-agent-builder.apiName.updatePrompt": "更新系统提示词",
|
||||
"builtins.lobe-agent-builder.title": "助理构建专家",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.editLocalFile": "编辑文件",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.executeCode": "执行代码",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.exportFile": "导出文件",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.getCommandOutput": "获取代码输出",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.globLocalFiles": "匹配搜索文件",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.grepContent": "搜索内容",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.killCommand": "终止代码执行",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.listLocalFiles": "查看文件列表",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.moveLocalFiles": "移动文件",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.readLocalFile": "读取文件内容",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.renameLocalFile": "重命名",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.runCommand": "执行代码",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.searchLocalFiles": "搜索文件",
|
||||
"builtins.lobe-cloud-code-interpreter.apiName.writeLocalFile": "写入文件",
|
||||
"builtins.lobe-cloud-code-interpreter.title": "云端沙箱",
|
||||
"builtins.lobe-group-agent-builder.apiName.getAvailableModels": "获取可用模型",
|
||||
"builtins.lobe-group-agent-builder.apiName.installPlugin": "安装技能",
|
||||
"builtins.lobe-group-agent-builder.apiName.inviteAgent": "邀请成员",
|
||||
|
||||
+10
-10
@@ -14,15 +14,6 @@
|
||||
"tts",
|
||||
"stt"
|
||||
],
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"packages/business/*",
|
||||
"e2e",
|
||||
"apps/desktop/src/main"
|
||||
],
|
||||
"overrides": {
|
||||
"stylelint-config-clean-order": "7.0.0"
|
||||
},
|
||||
"homepage": "https://github.com/lobehub/lobe-chat",
|
||||
"bugs": {
|
||||
"url": "https://github.com/lobehub/lobe-chat/issues/new/choose"
|
||||
@@ -34,6 +25,12 @@
|
||||
"license": "MIT",
|
||||
"author": "LobeHub <i@lobehub.com>",
|
||||
"sideEffects": false,
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"packages/business/*",
|
||||
"e2e",
|
||||
"apps/desktop/src/main"
|
||||
],
|
||||
"scripts": {
|
||||
"prebuild": "tsx scripts/prebuild.mts && npm run lint",
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=6144 next build --webpack",
|
||||
@@ -134,6 +131,9 @@
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"stylelint-config-clean-order": "7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@ant-design/pro-components": "^2.8.10",
|
||||
@@ -451,4 +451,4 @@
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ export const LocalSystemManifest: BuiltinToolManifest = {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
required: ['command'],
|
||||
required: ['description', 'command'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -172,6 +172,42 @@ export interface BuiltinPlaceholderProps<T extends Record<string, any> = any> {
|
||||
|
||||
export type BuiltinPlaceholder = (props: BuiltinPlaceholderProps) => ReactNode;
|
||||
|
||||
// ==================== Inspector Renderer Types ====================
|
||||
|
||||
export interface BuiltinInspectorProps<Arguments = any, State = any> {
|
||||
apiName: string;
|
||||
args: Arguments;
|
||||
identifier: string;
|
||||
/**
|
||||
* Whether the tool arguments are currently streaming (not yet complete)
|
||||
* Use this to distinguish between "arguments streaming" vs "tool executing" states
|
||||
*/
|
||||
isArgumentsStreaming?: boolean;
|
||||
isLoading?: boolean;
|
||||
partialArgs?: Arguments;
|
||||
pluginState?: State;
|
||||
result?: { content: string | null; error?: any };
|
||||
}
|
||||
|
||||
export type BuiltinInspector = <A = any, S = any>(props: BuiltinInspectorProps<A, S>) => ReactNode;
|
||||
|
||||
// ==================== Streaming Renderer Types ====================
|
||||
|
||||
/**
|
||||
* Props for streaming render components
|
||||
* Note: During streaming phase, only basic info is available.
|
||||
* pluginState and streaming content should be fetched from store inside the component.
|
||||
*/
|
||||
export interface BuiltinStreamingProps<Arguments = any> {
|
||||
apiName: string;
|
||||
args: Arguments;
|
||||
identifier: string;
|
||||
messageId: string;
|
||||
toolCallId: string;
|
||||
}
|
||||
|
||||
export type BuiltinStreaming = <A = any>(props: BuiltinStreamingProps<A>) => ReactNode;
|
||||
|
||||
export interface BuiltinServerRuntimeOutput {
|
||||
content: string;
|
||||
error?: any;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { parse } from 'partial-json';
|
||||
|
||||
export const safeParseJSON = <T = Record<string, any>>(text?: string) => {
|
||||
if (typeof text !== 'string') return undefined;
|
||||
|
||||
@@ -10,3 +12,11 @@ export const safeParseJSON = <T = Record<string, any>>(text?: string) => {
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
export const safeParsePartialJSON = <T = Record<string, any>>(text?: string): T | undefined => {
|
||||
try {
|
||||
return parse(text || '{}') as T;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
+14
-5
@@ -1,14 +1,17 @@
|
||||
import { MemorySourceType } from '@lobechat/types';
|
||||
import { createWorkflow, serveMany } from '@upstash/workflow/nextjs';
|
||||
|
||||
import {
|
||||
MemoryExtractionExecutor,
|
||||
MemoryExtractionPayloadInput,
|
||||
type MemoryExtractionPayloadInput,
|
||||
normalizeMemoryExtractionPayload,
|
||||
} from '@/server/services/memory/userMemory/extract';
|
||||
import { MemorySourceType } from '@lobechat/types';
|
||||
|
||||
export const { POST } = serveMany({
|
||||
'memory:user-memory:extract:users:topics:extract-layers:other-layers': createWorkflow<MemoryExtractionPayloadInput, { processed: number, results: any[] }>(async (context) => {
|
||||
'memory:user-memory:extract:users:topics:extract-layers:other-layers': createWorkflow<
|
||||
MemoryExtractionPayloadInput,
|
||||
{ processed: number; results: any[] }
|
||||
>(async (context) => {
|
||||
const params = normalizeMemoryExtractionPayload(context.requestPayload || {});
|
||||
if (!params.userIds.length) {
|
||||
return { message: 'No user id provided for topic batch.', processed: 0, results: [] };
|
||||
@@ -23,7 +26,13 @@ export const { POST } = serveMany({
|
||||
const userId = params.userIds[0];
|
||||
const executor = await MemoryExtractionExecutor.create();
|
||||
|
||||
const results: { extracted: boolean; layers: Record<string, number>; memoryIds: string[]; topicId: string, userId: string }[] = [];
|
||||
const results: {
|
||||
extracted: boolean;
|
||||
layers: Record<string, number>;
|
||||
memoryIds: string[];
|
||||
topicId: string;
|
||||
userId: string;
|
||||
}[] = [];
|
||||
|
||||
for (const topicId of params.topicIds) {
|
||||
const extracted = await context.run(
|
||||
@@ -45,5 +54,5 @@ export const { POST } = serveMany({
|
||||
}
|
||||
|
||||
return { processed: results.length, results };
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
+28
-7
@@ -1,12 +1,12 @@
|
||||
import { LayersEnum, MemorySourceType } from '@lobechat/types';
|
||||
import { createWorkflow, serveMany } from '@upstash/workflow/nextjs';
|
||||
|
||||
import {
|
||||
MemoryExtractionExecutor,
|
||||
MemoryExtractionPayloadInput,
|
||||
normalizeMemoryExtractionPayload,
|
||||
type MemoryExtractionPayloadInput,
|
||||
TOPIC_WORKFLOW_NAMES,
|
||||
normalizeMemoryExtractionPayload,
|
||||
} from '@/server/services/memory/userMemory/extract';
|
||||
import { LayersEnum, MemorySourceType } from '@lobechat/types';
|
||||
|
||||
type ExtractionResult = {
|
||||
extracted: boolean;
|
||||
@@ -95,13 +95,28 @@ const orchestratorWorkflow = createWorkflow<
|
||||
>(async (context) => {
|
||||
const params = normalizeMemoryExtractionPayload(context.requestPayload || {});
|
||||
if (!params.userIds.length) {
|
||||
return { message: 'No user id provided for topic batch.', nextIdentityCursor: null, processedCep: 0, processedIdentity: 0 };
|
||||
return {
|
||||
message: 'No user id provided for topic batch.',
|
||||
nextIdentityCursor: null,
|
||||
processedCep: 0,
|
||||
processedIdentity: 0,
|
||||
};
|
||||
}
|
||||
if (!params.topicIds.length) {
|
||||
return { message: 'No topic ids provided for extraction.', nextIdentityCursor: null, processedCep: 0, processedIdentity: 0 };
|
||||
return {
|
||||
message: 'No topic ids provided for extraction.',
|
||||
nextIdentityCursor: null,
|
||||
processedCep: 0,
|
||||
processedIdentity: 0,
|
||||
};
|
||||
}
|
||||
if (!params.sources.includes(MemorySourceType.ChatTopic)) {
|
||||
return { message: 'Source not supported in topic batch.', nextIdentityCursor: null, processedCep: 0, processedIdentity: 0 };
|
||||
return {
|
||||
message: 'Source not supported in topic batch.',
|
||||
nextIdentityCursor: null,
|
||||
processedCep: 0,
|
||||
processedIdentity: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const userId = params.userIds[0];
|
||||
@@ -119,7 +134,13 @@ const orchestratorWorkflow = createWorkflow<
|
||||
|
||||
const invokeIdentity = (topicId: string, pointer: number) =>
|
||||
context.invoke(`memory:user-memory:extract:users:${userId}:topics:identity:${pointer}`, {
|
||||
body: { ...params, identityCursor: undefined, topicIds: [topicId], userId, userIds: [userId] },
|
||||
body: {
|
||||
...params,
|
||||
identityCursor: undefined,
|
||||
topicIds: [topicId],
|
||||
userId,
|
||||
userIds: [userId],
|
||||
},
|
||||
flowControl: { key: `memory:user:${userId}:identity`, parallelism: 1 },
|
||||
workflow: identityWorkflow,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ModelUsage } from '@/types/index';
|
||||
import { type ModelUsage } from '@/types/index';
|
||||
|
||||
interface ChargeParams {
|
||||
metadata: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars */
|
||||
import { Plans, ReferralStatusString } from '@lobechat/types';
|
||||
import { type Plans, type ReferralStatusString } from '@lobechat/types';
|
||||
|
||||
export async function getReferralStatus(userId: string): Promise<ReferralStatusString | undefined> {
|
||||
return undefined;
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import { Modal } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
|
||||
import FileViewer from '@/features/FileViewer';
|
||||
import { UploadFileItem } from '@/types/files/upload';
|
||||
import { type UploadFileItem } from '@/types/files/upload';
|
||||
|
||||
interface FilePreviewModalProps {
|
||||
file: UploadFileItem;
|
||||
@@ -12,7 +13,6 @@ interface FilePreviewModalProps {
|
||||
}
|
||||
|
||||
const FilePreviewModal = memo<FilePreviewModalProps>(({ file, open, onClose }) => {
|
||||
|
||||
// Get the best available URL for preview
|
||||
const previewUrl = file.previewUrl || file.fileUrl || file.base64Url || '';
|
||||
|
||||
|
||||
@@ -1,39 +1,74 @@
|
||||
import { type ToolIntervention } from '@lobechat/types';
|
||||
import { safeParseJSON, safeParsePartialJSON } from '@lobechat/utils';
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { LOADING_FLAT } from '@/const/message';
|
||||
import { getBuiltinInspector } from '@/tools/inspectors';
|
||||
|
||||
import StatusIndicator from './StatusIndicator';
|
||||
import ToolTitle from './ToolTitle';
|
||||
|
||||
interface InspectorProps {
|
||||
apiName: string;
|
||||
arguments?: string;
|
||||
identifier: string;
|
||||
intervention?: ToolIntervention;
|
||||
/**
|
||||
* Whether the tool arguments are currently streaming
|
||||
*/
|
||||
isArgumentsStreaming?: boolean;
|
||||
result?: { content: string | null; error?: any; state?: any };
|
||||
}
|
||||
|
||||
const Inspectors = memo<InspectorProps>(({ identifier, apiName, result, intervention }) => {
|
||||
const hasError = !!result?.error;
|
||||
const hasSuccessResult = !!result?.content && result.content !== LOADING_FLAT;
|
||||
const hasResult = hasSuccessResult || hasError;
|
||||
const Inspectors = memo<InspectorProps>(
|
||||
({ identifier, apiName, arguments: argsStr, result, intervention, isArgumentsStreaming }) => {
|
||||
const hasError = !!result?.error;
|
||||
const hasSuccessResult = !!result?.content && result.content !== LOADING_FLAT;
|
||||
const hasResult = hasSuccessResult || hasError;
|
||||
|
||||
const isPending = intervention?.status === 'pending';
|
||||
const isAborted = intervention?.status === 'aborted';
|
||||
const isTitleLoading = !hasResult && !isPending && !isAborted;
|
||||
const isPending = intervention?.status === 'pending';
|
||||
const isAborted = intervention?.status === 'aborted';
|
||||
|
||||
return (
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<StatusIndicator intervention={intervention} result={result} />
|
||||
<ToolTitle
|
||||
apiName={apiName}
|
||||
identifier={identifier}
|
||||
isAborted={isAborted}
|
||||
isLoading={isTitleLoading}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
// Distinguish between arguments streaming and tool executing
|
||||
const isToolExecuting = !hasResult && !isPending && !isAborted && !isArgumentsStreaming;
|
||||
const isTitleLoading = isArgumentsStreaming || isToolExecuting;
|
||||
|
||||
// Check for custom inspector renderer
|
||||
const CustomInspector = getBuiltinInspector(identifier, apiName);
|
||||
|
||||
if (CustomInspector) {
|
||||
const args = safeParseJSON(argsStr);
|
||||
const partialJson = safeParsePartialJSON(argsStr);
|
||||
return (
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<StatusIndicator intervention={intervention} result={result} />
|
||||
<CustomInspector
|
||||
apiName={apiName}
|
||||
args={args || {}}
|
||||
identifier={identifier}
|
||||
isArgumentsStreaming={isArgumentsStreaming}
|
||||
isLoading={isTitleLoading}
|
||||
partialArgs={partialJson}
|
||||
pluginState={result?.state}
|
||||
result={result}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox align={'center'} gap={6} horizontal>
|
||||
<StatusIndicator intervention={intervention} result={result} />
|
||||
<ToolTitle
|
||||
apiName={apiName}
|
||||
identifier={identifier}
|
||||
isAborted={isAborted}
|
||||
isLoading={isTitleLoading}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default Inspectors;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { LOADING_FLAT } from '@lobechat/const';
|
||||
import { type ChatToolResult, type ToolIntervention } from '@lobechat/types';
|
||||
import { safeParsePartialJSON } from '@lobechat/utils';
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { Suspense, memo } from 'react';
|
||||
|
||||
import { getBuiltinStreaming } from '@/tools/streamings';
|
||||
|
||||
import AbortResponse from './AbortResponse';
|
||||
import CustomRender from './CustomRender';
|
||||
import ErrorResponse from './ErrorResponse';
|
||||
@@ -16,6 +19,7 @@ interface RenderProps {
|
||||
arguments?: string;
|
||||
identifier: string;
|
||||
intervention?: ToolIntervention;
|
||||
isArgumentsStreaming?: boolean;
|
||||
/**
|
||||
* ContentBlock ID (not the group message ID)
|
||||
*/
|
||||
@@ -47,7 +51,31 @@ const Render = memo<RenderProps>(
|
||||
type,
|
||||
intervention,
|
||||
toolMessageId,
|
||||
isArgumentsStreaming,
|
||||
}) => {
|
||||
// Handle arguments streaming state
|
||||
if (isArgumentsStreaming) {
|
||||
// Check if there's a custom streaming renderer for this tool
|
||||
const StreamingRenderer = getBuiltinStreaming(identifier, apiName);
|
||||
|
||||
if (StreamingRenderer) {
|
||||
const args = safeParsePartialJSON(requestArgs);
|
||||
|
||||
return (
|
||||
<StreamingRenderer
|
||||
apiName={apiName}
|
||||
args={args}
|
||||
identifier={identifier}
|
||||
messageId={messageId}
|
||||
toolCallId={toolCallId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// No custom streaming renderer, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
if (toolMessageId && intervention?.status === 'pending') {
|
||||
return (
|
||||
<Intervention
|
||||
|
||||
@@ -7,6 +7,7 @@ import { memo, useEffect, useState } from 'react';
|
||||
import Actions from '@/features/Conversation/Messages/AssistantGroup/Tool/Actions';
|
||||
import { useToolStore } from '@/store/tool';
|
||||
import { toolSelectors } from '@/store/tool/selectors';
|
||||
import { getBuiltinStreaming } from '@/tools/streamings';
|
||||
|
||||
import Inspectors from './Inspector';
|
||||
|
||||
@@ -68,6 +69,16 @@ const Tool = memo<GroupToolProps>(
|
||||
|
||||
const showCustomPluginRender = !isPending && !isReject && !isAbort;
|
||||
|
||||
let isArgumentsStreaming = false;
|
||||
try {
|
||||
JSON.parse(requestArgs || '{}');
|
||||
} catch {
|
||||
isArgumentsStreaming = true;
|
||||
}
|
||||
|
||||
const hasStreamingRenderer = !!getBuiltinStreaming(identifier, apiName);
|
||||
const forceShowStreamingRender = isArgumentsStreaming && hasStreamingRenderer;
|
||||
|
||||
// Wrap handleExpand to prevent collapsing when alwaysExpand is set
|
||||
const wrappedHandleExpand = (expand?: boolean) => {
|
||||
// Block collapse action when alwaysExpand is set
|
||||
@@ -88,6 +99,10 @@ const Tool = memo<GroupToolProps>(
|
||||
onCollapsibleChange?.(!isAlwaysExpand);
|
||||
}, [isAlwaysExpand, onCollapsibleChange]);
|
||||
|
||||
useEffect(() => {
|
||||
handleExpand?.(forceShowStreamingRender);
|
||||
}, [forceShowStreamingRender]);
|
||||
|
||||
return (
|
||||
<AccordionItem
|
||||
action={
|
||||
@@ -108,8 +123,10 @@ const Tool = memo<GroupToolProps>(
|
||||
title={
|
||||
<Inspectors
|
||||
apiName={apiName}
|
||||
arguments={requestArgs}
|
||||
identifier={identifier}
|
||||
intervention={intervention}
|
||||
isArgumentsStreaming={isArgumentsStreaming}
|
||||
result={result}
|
||||
/>
|
||||
}
|
||||
@@ -131,6 +148,7 @@ const Tool = memo<GroupToolProps>(
|
||||
arguments={requestArgs}
|
||||
identifier={identifier}
|
||||
intervention={intervention}
|
||||
isArgumentsStreaming={isArgumentsStreaming}
|
||||
messageId={assistantMessageId}
|
||||
result={result}
|
||||
setShowPluginRender={setShowPluginRender}
|
||||
|
||||
@@ -140,7 +140,13 @@ const ListView = memo<ListViewProps>(
|
||||
|
||||
return (
|
||||
<Flexbox height={'100%'}>
|
||||
<Flexbox align={'center'} className={styles.header} horizontal paddingInline={8} style={{ fontSize: 12 }}>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={styles.header}
|
||||
horizontal
|
||||
paddingInline={8}
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
<Center height={40} style={{ paddingInline: 4 }}>
|
||||
<Checkbox
|
||||
checked={allSelected}
|
||||
@@ -180,7 +186,7 @@ const ListView = memo<ListViewProps>(
|
||||
return (
|
||||
<Center className={styles.loadMoreContainer} key="load-more">
|
||||
<Button loading={isLoadingMore} onClick={handleLoadMore} type="default">
|
||||
{t('loadMore', { ns: 'file', defaultValue: 'Load More' })}
|
||||
{t('loadMore', { defaultValue: 'Load More', ns: 'file' })}
|
||||
</Button>
|
||||
</Center>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { SiGithub, SiX } from '@icons-pack/react-simple-icons';
|
||||
import { Center, Flexbox, Icon, Input, Modal, Text, TextArea, Tooltip } from '@lobehub/ui';
|
||||
import { App, Divider, Form, Upload, type UploadProps } from 'antd';
|
||||
import { App, Form, Upload, type UploadProps } from 'antd';
|
||||
import { useTheme } from 'antd-style';
|
||||
import { CircleHelp, Globe, ImagePlus, Trash2 } from 'lucide-react';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
@@ -16,6 +16,21 @@ export default {
|
||||
'builtins.lobe-agent-builder.apiName.updateMeta': 'Update metadata',
|
||||
'builtins.lobe-agent-builder.apiName.updatePrompt': 'Update system prompt',
|
||||
'builtins.lobe-agent-builder.title': 'Agent Builder Expert',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.editLocalFile': 'Edit file',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.executeCode': 'Execute code',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.exportFile': 'Export file',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.getCommandOutput': 'Get command output',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.globLocalFiles': 'Glob search files',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.grepContent': 'Search content',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.killCommand': 'Terminate command',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.listLocalFiles': 'List files',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.moveLocalFiles': 'Move files',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.readLocalFile': 'Read file content',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.renameLocalFile': 'Rename',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.runCommand': 'Run command',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.searchLocalFiles': 'Search files',
|
||||
'builtins.lobe-cloud-code-interpreter.apiName.writeLocalFile': 'Write file',
|
||||
'builtins.lobe-cloud-code-interpreter.title': 'Cloud Sandbox',
|
||||
'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',
|
||||
@@ -176,7 +191,8 @@ export default {
|
||||
'dev.mcp.headers.desc': 'Enter HTTP headers',
|
||||
'dev.mcp.headers.label': 'HTTP Headers',
|
||||
'dev.mcp.identifier.desc': 'Name for this MCP (English characters only)',
|
||||
'dev.mcp.identifier.invalid': 'Identifier must contain only letters, numbers, hyphens, underscores',
|
||||
'dev.mcp.identifier.invalid':
|
||||
'Identifier must contain only letters, numbers, hyphens, underscores',
|
||||
'dev.mcp.identifier.label': 'MCP name',
|
||||
'dev.mcp.identifier.placeholder': 'e.g. my-mcp-plugin',
|
||||
'dev.mcp.identifier.required': 'Enter MCP identifier',
|
||||
@@ -287,8 +303,7 @@ export default {
|
||||
'mcpInstall.configurationDescription': 'Configure required parameters for this MCP',
|
||||
'mcpInstall.configurationRequired': 'Configure parameters',
|
||||
'mcpInstall.continueInstall': 'Continue',
|
||||
'mcpInstall.dependenciesDescription':
|
||||
'Install required dependencies, then recheck to continue.',
|
||||
'mcpInstall.dependenciesDescription': 'Install required dependencies, then recheck to continue.',
|
||||
'mcpInstall.dependenciesRequired': 'Install system dependencies',
|
||||
'mcpInstall.dependencyStatus.installed': 'Installed',
|
||||
'mcpInstall.dependencyStatus.notInstalled': 'Not installed',
|
||||
@@ -352,8 +367,7 @@ export default {
|
||||
'protocolInstall.meta.source': 'Source',
|
||||
'protocolInstall.meta.version': 'Version',
|
||||
'protocolInstall.official.badge': 'LobeHub Official Skill',
|
||||
'protocolInstall.official.description':
|
||||
'Official LobeHub Skill, verified and security-checked.',
|
||||
'protocolInstall.official.description': 'Official LobeHub Skill, verified and security-checked.',
|
||||
'protocolInstall.official.loadingMessage': 'Loading Skill details…',
|
||||
'protocolInstall.official.loadingTitle': 'Loading',
|
||||
'protocolInstall.official.title': 'Install official Skill',
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
'use client';
|
||||
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
import { type ExecuteCodeState } from '../../type';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
interface ExecuteCodeParams {
|
||||
code: string;
|
||||
description: string;
|
||||
language?: 'javascript' | 'python' | 'typescript';
|
||||
}
|
||||
|
||||
export const ExecuteCodeInspector = memo<
|
||||
BuiltinInspectorProps<ExecuteCodeParams, ExecuteCodeState>
|
||||
>(({ args, partialArgs, isArgumentsStreaming }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
if (isArgumentsStreaming && !partialArgs?.description)
|
||||
return (
|
||||
<div className={cx(styles.root, styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-cloud-code-interpreter.title')}</span>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span>{t('builtins.lobe-cloud-code-interpreter.apiName.executeCode')}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const displayText = args?.description || partialArgs?.description || '';
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isArgumentsStreaming && styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-cloud-code-interpreter.apiName.executeCode')}</span>
|
||||
{displayText && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{displayText}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
ExecuteCodeInspector.displayName = 'ExecuteCodeInspector';
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CodeInterpreterApiName } from '../index';
|
||||
import { ExecuteCodeInspector } from './ExecuteCode';
|
||||
|
||||
/**
|
||||
* Code Interpreter Inspector Components Registry
|
||||
*/
|
||||
export const CodeInterpreterInspectors = {
|
||||
[CodeInterpreterApiName.executeCode]: ExecuteCodeInspector,
|
||||
};
|
||||
@@ -1,11 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
|
||||
import { type BuiltinRenderProps } from '@lobechat/types';
|
||||
import { ActionIcon, Block, Flexbox, Highlighter, Text } from '@lobehub/ui';
|
||||
import type { BuiltinRenderProps } from '@lobechat/types';
|
||||
import { Block, Flexbox, Highlighter } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { type ExecuteCodeState } from '../../type';
|
||||
|
||||
@@ -40,84 +38,41 @@ interface ExecuteCodeParams {
|
||||
language?: 'javascript' | 'python' | 'typescript';
|
||||
}
|
||||
|
||||
const languageDisplayNames: Record<string, string> = {
|
||||
javascript: 'JavaScript',
|
||||
python: 'Python',
|
||||
typescript: 'TypeScript',
|
||||
};
|
||||
|
||||
const ExecuteCode = memo<BuiltinRenderProps<ExecuteCodeParams, ExecuteCodeState>>(
|
||||
({ args, pluginState }) => {
|
||||
const { styles, theme } = useStyles();
|
||||
const isSuccess = pluginState?.success;
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { styles } = useStyles();
|
||||
|
||||
const language = args.language || 'python';
|
||||
const displayLanguage = languageDisplayNames[language] || language;
|
||||
|
||||
const statusMessage = pluginState?.success
|
||||
? 'Execution completed'
|
||||
: pluginState?.error || 'Execution failed';
|
||||
|
||||
return (
|
||||
<Flexbox className={styles.container} gap={8}>
|
||||
{/* Header: Language + Status */}
|
||||
<Flexbox align={'center'} className={styles.header} horizontal justify={'space-between'}>
|
||||
<Flexbox gap={8} horizontal>
|
||||
<Flexbox gap={4} horizontal>
|
||||
{pluginState === undefined ? null : isSuccess ? (
|
||||
<CheckCircleFilled
|
||||
className={styles.statusIcon}
|
||||
style={{ color: theme.colorSuccess }}
|
||||
/>
|
||||
) : (
|
||||
<CloseCircleFilled
|
||||
className={styles.statusIcon}
|
||||
style={{ color: theme.colorError }}
|
||||
/>
|
||||
)}
|
||||
<Text className={styles.head}>{displayLanguage}</Text>
|
||||
</Flexbox>
|
||||
<Text className={styles.head} type={'secondary'}>
|
||||
{statusMessage}
|
||||
</Text>
|
||||
</Flexbox>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<ActionIcon
|
||||
className={`action-icon`}
|
||||
icon={expanded ? ChevronUp : ChevronDown}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
size={'small'}
|
||||
style={{ opacity: expanded ? 1 : undefined }}
|
||||
title={expanded ? 'Collapse' : 'Expand'}
|
||||
/>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
|
||||
{/* Code & Output */}
|
||||
{expanded && (
|
||||
<Block gap={8} padding={8} variant={'outlined'}>
|
||||
<Block gap={8} padding={8} variant={'outlined'}>
|
||||
<Highlighter
|
||||
language={language}
|
||||
showLanguage={false}
|
||||
style={{ maxHeight: 200, overflow: 'auto', paddingInline: 8 }}
|
||||
variant={'borderless'}
|
||||
wrap
|
||||
>
|
||||
{args.code}
|
||||
</Highlighter>
|
||||
{pluginState?.output && (
|
||||
<Highlighter
|
||||
language={language}
|
||||
language={'text'}
|
||||
showLanguage={false}
|
||||
style={{ paddingInline: 8 }}
|
||||
variant={'borderless'}
|
||||
style={{ maxHeight: 200, overflow: 'auto', paddingInline: 8 }}
|
||||
variant={'filled'}
|
||||
wrap
|
||||
>
|
||||
{args.code}
|
||||
{pluginState.output}
|
||||
</Highlighter>
|
||||
{pluginState?.output && (
|
||||
<Highlighter language={'text'} showLanguage={false} variant={'filled'} wrap>
|
||||
{pluginState.output}
|
||||
</Highlighter>
|
||||
)}
|
||||
{pluginState?.stderr && (
|
||||
<Highlighter language={'text'} showLanguage={false} variant={'filled'} wrap>
|
||||
{pluginState.stderr}
|
||||
</Highlighter>
|
||||
)}
|
||||
</Block>
|
||||
)}
|
||||
)}
|
||||
{pluginState?.stderr && (
|
||||
<Highlighter language={'text'} showLanguage={false} variant={'filled'} wrap>
|
||||
{pluginState.stderr}
|
||||
</Highlighter>
|
||||
)}
|
||||
</Block>
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client';
|
||||
|
||||
import { type BuiltinStreamingProps } from '@lobechat/types';
|
||||
import { Highlighter } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
|
||||
interface ExecuteCodeParams {
|
||||
code?: string;
|
||||
description?: string;
|
||||
language?: 'javascript' | 'python' | 'typescript';
|
||||
}
|
||||
|
||||
const languageDisplayNames: Record<string, string> = {
|
||||
javascript: 'JavaScript',
|
||||
python: 'Python',
|
||||
typescript: 'TypeScript',
|
||||
};
|
||||
|
||||
export const ExecuteCodeStreaming = memo<BuiltinStreamingProps<ExecuteCodeParams>>(({ args }) => {
|
||||
const { code, language = 'python' } = args || {};
|
||||
|
||||
const displayLanguage = languageDisplayNames[language] || language;
|
||||
|
||||
// Don't render if no code yet
|
||||
if (!code) return null;
|
||||
|
||||
return (
|
||||
<Highlighter
|
||||
animated
|
||||
language={displayLanguage}
|
||||
showLanguage={false}
|
||||
style={{ padding: '4px 8px' }}
|
||||
variant={'outlined'}
|
||||
wrap
|
||||
>
|
||||
{code}
|
||||
</Highlighter>
|
||||
);
|
||||
});
|
||||
|
||||
ExecuteCodeStreaming.displayName = 'ExecuteCodeStreaming';
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CodeInterpreterApiName } from '../index';
|
||||
import { ExecuteCodeStreaming } from './ExecuteCode';
|
||||
|
||||
/**
|
||||
* Code Interpreter Streaming Components Registry
|
||||
*/
|
||||
export const CodeInterpreterStreamings = {
|
||||
[CodeInterpreterApiName.executeCode]: ExecuteCodeStreaming,
|
||||
};
|
||||
@@ -21,6 +21,7 @@ export const CodeInterpreterApiName = {
|
||||
|
||||
export const CodeInterpreterIdentifier = 'lobe-cloud-code-interpreter';
|
||||
|
||||
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
||||
export const CodeInterpreterManifest: BuiltinToolManifest = {
|
||||
api: [
|
||||
{
|
||||
@@ -30,8 +31,9 @@ export const CodeInterpreterManifest: BuiltinToolManifest = {
|
||||
name: CodeInterpreterApiName.executeCode,
|
||||
parameters: {
|
||||
properties: {
|
||||
code: {
|
||||
description: 'The code to execute',
|
||||
description: {
|
||||
description:
|
||||
'A brief description of what this code does (required for user understanding)',
|
||||
type: 'string',
|
||||
},
|
||||
language: {
|
||||
@@ -39,8 +41,12 @@ export const CodeInterpreterManifest: BuiltinToolManifest = {
|
||||
enum: ['python', 'javascript', 'typescript'],
|
||||
type: 'string',
|
||||
},
|
||||
code: {
|
||||
description: 'The code to execute',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['code'],
|
||||
required: ['description', 'language', 'code'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
|
||||
import { type BuiltinInspector } from '@lobechat/types';
|
||||
|
||||
import { CodeInterpreterInspectors } from './code-interpreter/Inspector';
|
||||
import { CodeInterpreterIdentifier } from './code-interpreter/index';
|
||||
import { LocalSystemInspectors } from './local-system/Inspector';
|
||||
import { WebBrowsingInspectors } from './web-browsing/Inspector';
|
||||
import { WebBrowsingManifest } from './web-browsing/index';
|
||||
|
||||
/**
|
||||
* Builtin tools inspector registry
|
||||
* Organized by toolset (identifier) -> API name
|
||||
*
|
||||
* Inspector components are used to customize the title/header area
|
||||
* of tool calls in the conversation UI.
|
||||
*/
|
||||
const BuiltinToolInspectors: Record<string, Record<string, BuiltinInspector>> = {
|
||||
[CodeInterpreterIdentifier]: CodeInterpreterInspectors as Record<string, BuiltinInspector>,
|
||||
[LocalSystemManifest.identifier]: LocalSystemInspectors as Record<string, BuiltinInspector>,
|
||||
[WebBrowsingManifest.identifier]: WebBrowsingInspectors as Record<string, BuiltinInspector>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get builtin inspector component for a specific API
|
||||
* @param identifier - Tool identifier (e.g., 'lobe-code-interpreter')
|
||||
* @param apiName - API name (e.g., 'executeCode')
|
||||
*/
|
||||
export const getBuiltinInspector = (
|
||||
identifier?: string,
|
||||
apiName?: string,
|
||||
): BuiltinInspector | undefined => {
|
||||
if (!identifier || !apiName) return undefined;
|
||||
|
||||
const toolset = BuiltinToolInspectors[identifier];
|
||||
if (!toolset) return undefined;
|
||||
|
||||
return toolset[apiName];
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import { type EditLocalFileState } from '@lobechat/builtin-tool-local-system';
|
||||
import { type EditLocalFileParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import path from 'path-browserify-esm';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
export const EditLocalFileInspector = memo<
|
||||
BuiltinInspectorProps<EditLocalFileParams, EditLocalFileState>
|
||||
>(({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
// Show filename with parent directory for context
|
||||
let displayPath = '';
|
||||
if (args?.file_path) {
|
||||
const { base, dir } = path.parse(args.file_path);
|
||||
const parentDir = path.basename(dir);
|
||||
displayPath = parentDir ? `${parentDir}/${base}` : base;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.editLocalFile')}</span>
|
||||
{displayPath && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{displayPath}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
EditLocalFileInspector.displayName = 'EditLocalFileInspector';
|
||||
@@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { type GlobFilesState } from '@lobechat/builtin-tool-local-system';
|
||||
import { type GlobFilesParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
export const GlobLocalFilesInspector = memo<BuiltinInspectorProps<GlobFilesParams, GlobFilesState>>(
|
||||
({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
const pattern = args?.pattern || '';
|
||||
|
||||
// When loading, show "本地系统 > 匹配搜索文件"
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cx(styles.root, styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.title')}</span>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span>{t('builtins.lobe-local-system.apiName.globLocalFiles')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.globLocalFiles')}</span>
|
||||
{pattern && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{pattern}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
GlobLocalFilesInspector.displayName = 'GlobLocalFilesInspector';
|
||||
@@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { type GrepContentState } from '@lobechat/builtin-tool-local-system';
|
||||
import { type GrepContentParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
export const GrepContentInspector = memo<
|
||||
BuiltinInspectorProps<GrepContentParams, GrepContentState>
|
||||
>(({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
const pattern = args?.pattern || '';
|
||||
|
||||
// When loading, show "本地系统 > 搜索内容"
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cx(styles.root, styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.title')}</span>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span>{t('builtins.lobe-local-system.apiName.grepContent')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.grepContent')}</span>
|
||||
{pattern && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{pattern}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
GrepContentInspector.displayName = 'GrepContentInspector';
|
||||
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import type { LocalReadFileState } from '@lobechat/builtin-tool-local-system';
|
||||
import { type LocalReadFileParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import path from 'path-browserify-esm';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
export const ReadLocalFileInspector = memo<
|
||||
BuiltinInspectorProps<LocalReadFileParams, LocalReadFileState>
|
||||
>(({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
// Show filename with parent directory for context
|
||||
let displayPath = '';
|
||||
if (args?.path) {
|
||||
const { base, dir } = path.parse(args.path);
|
||||
const parentDir = path.basename(dir);
|
||||
displayPath = parentDir ? `${parentDir}/${base}` : base;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.root, isLoading && styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.readLocalFile')}</span>
|
||||
{displayPath && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{displayPath}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
ReadLocalFileInspector.displayName = 'ReadLocalFileInspector';
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import { type RunCommandParams, type RunCommandResult } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
interface RunCommandState {
|
||||
message: string;
|
||||
result: RunCommandResult;
|
||||
}
|
||||
|
||||
export const RunCommandInspector = memo<BuiltinInspectorProps<RunCommandParams, RunCommandState>>(
|
||||
({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
// Show description if available, otherwise show command
|
||||
const displayText = args?.description || args?.command || '';
|
||||
|
||||
// When loading, show "Local System > 执行命令"
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cx(styles.root, styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.title')}</span>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span>{t('builtins.lobe-local-system.apiName.runCommand')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.runCommand')}</span>
|
||||
{displayText && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{displayText}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
RunCommandInspector.displayName = 'RunCommandInspector';
|
||||
|
||||
export default RunCommandInspector;
|
||||
@@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { type LocalFileSearchState } from '@lobechat/builtin-tool-local-system';
|
||||
import { type LocalSearchFilesParams } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
export const SearchLocalFilesInspector = memo<
|
||||
BuiltinInspectorProps<LocalSearchFilesParams, LocalFileSearchState>
|
||||
>(({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
const keywords = args?.keywords || '';
|
||||
|
||||
// When loading, show "本地系统 > 搜索文件"
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cx(styles.root, styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-local-system.title')}</span>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span>{t('builtins.lobe-local-system.apiName.searchLocalFiles')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<span>{t('builtins.lobe-local-system.apiName.searchLocalFiles')}</span>
|
||||
{keywords && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{keywords}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SearchLocalFilesInspector.displayName = 'SearchLocalFilesInspector';
|
||||
@@ -0,0 +1,20 @@
|
||||
import { LocalSystemApiName } from '@lobechat/builtin-tool-local-system';
|
||||
|
||||
import { EditLocalFileInspector } from './EditLocalFile';
|
||||
import { GlobLocalFilesInspector } from './GlobLocalFiles';
|
||||
import { GrepContentInspector } from './GrepContent';
|
||||
import { ReadLocalFileInspector } from './ReadLocalFile';
|
||||
import { RunCommandInspector } from './RunCommand';
|
||||
import { SearchLocalFilesInspector } from './SearchLocalFiles';
|
||||
|
||||
/**
|
||||
* Local System Inspector Components Registry
|
||||
*/
|
||||
export const LocalSystemInspectors = {
|
||||
[LocalSystemApiName.editLocalFile]: EditLocalFileInspector,
|
||||
[LocalSystemApiName.globLocalFiles]: GlobLocalFilesInspector,
|
||||
[LocalSystemApiName.grepContent]: GrepContentInspector,
|
||||
[LocalSystemApiName.readLocalFile]: ReadLocalFileInspector,
|
||||
[LocalSystemApiName.runCommand]: RunCommandInspector,
|
||||
[LocalSystemApiName.searchLocalFiles]: SearchLocalFilesInspector,
|
||||
};
|
||||
@@ -1,10 +1,8 @@
|
||||
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
|
||||
import { type RunCommandParams, type RunCommandResult } from '@lobechat/electron-client-ipc';
|
||||
import { type BuiltinRenderProps } from '@lobechat/types';
|
||||
import { ActionIcon, Block, Flexbox, Highlighter, Text } from '@lobehub/ui';
|
||||
import { Block, Flexbox, Highlighter } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
container: css`
|
||||
@@ -39,69 +37,27 @@ interface RunCommandState {
|
||||
|
||||
const RunCommand = memo<BuiltinRenderProps<RunCommandParams, RunCommandState>>(
|
||||
({ args, pluginState }) => {
|
||||
const { styles, theme } = useStyles();
|
||||
const { result, message } = pluginState || {};
|
||||
const isSuccess = result?.success;
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { styles } = useStyles();
|
||||
const { result } = pluginState || {};
|
||||
|
||||
return (
|
||||
<Flexbox className={styles.container} gap={8}>
|
||||
{/* Header: Description + Status */}
|
||||
<Flexbox align={'center'} className={styles.header} horizontal justify={'space-between'}>
|
||||
<Flexbox gap={8} horizontal>
|
||||
<Flexbox gap={4} horizontal>
|
||||
{!result ? null : isSuccess ? (
|
||||
<CheckCircleFilled
|
||||
className={styles.statusIcon}
|
||||
style={{ color: theme.colorSuccess }}
|
||||
/>
|
||||
) : (
|
||||
<CloseCircleFilled
|
||||
className={styles.statusIcon}
|
||||
style={{ color: theme.colorError }}
|
||||
/>
|
||||
)}
|
||||
{args.description && <Text className={styles.head}>{args.description}</Text>}
|
||||
</Flexbox>
|
||||
{message && (
|
||||
<Flexbox align={'center'} gap={4} horizontal>
|
||||
<Text className={styles.head} type={'secondary'}>
|
||||
{message}
|
||||
</Text>
|
||||
</Flexbox>
|
||||
)}
|
||||
</Flexbox>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<ActionIcon
|
||||
className={`action-icon`}
|
||||
icon={expanded ? ChevronUp : ChevronDown}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
size={'small'}
|
||||
style={{ opacity: expanded ? 1 : undefined }}
|
||||
title={expanded ? 'Collapse' : 'Expand'}
|
||||
/>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
|
||||
{/* Command & Output */}
|
||||
{expanded && (
|
||||
<Block gap={8} padding={8} variant={'outlined'}>
|
||||
<Highlighter
|
||||
language={'sh'}
|
||||
showLanguage={false}
|
||||
style={{ paddingInline: 8 }}
|
||||
variant={'borderless'}
|
||||
wrap
|
||||
>
|
||||
{args.command}
|
||||
<Block gap={8} padding={8} variant={'outlined'}>
|
||||
<Highlighter
|
||||
language={'sh'}
|
||||
showLanguage={false}
|
||||
style={{ paddingInline: 8 }}
|
||||
variant={'borderless'}
|
||||
wrap
|
||||
>
|
||||
{args.command}
|
||||
</Highlighter>
|
||||
{result?.output && (
|
||||
<Highlighter language={'text'} showLanguage={false} variant={'filled'} wrap>
|
||||
{result.output}
|
||||
</Highlighter>
|
||||
{result?.output && (
|
||||
<Highlighter language={'text'} showLanguage={false} variant={'filled'} wrap>
|
||||
{result.output}
|
||||
</Highlighter>
|
||||
)}
|
||||
</Block>
|
||||
)}
|
||||
)}
|
||||
</Block>
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import { type BuiltinStreamingProps } from '@lobechat/types';
|
||||
import { Highlighter } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
|
||||
interface RunCommandParams {
|
||||
command?: string;
|
||||
description?: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export const RunCommandStreaming = memo<BuiltinStreamingProps<RunCommandParams>>(({ args }) => {
|
||||
const { command } = args || {};
|
||||
|
||||
// Don't render if no command yet
|
||||
if (!command) return null;
|
||||
|
||||
return (
|
||||
<Highlighter
|
||||
animated
|
||||
language={'sh'}
|
||||
showLanguage={false}
|
||||
style={{ padding: '4px 8px' }}
|
||||
variant={'outlined'}
|
||||
wrap
|
||||
>
|
||||
{command}
|
||||
</Highlighter>
|
||||
);
|
||||
});
|
||||
|
||||
RunCommandStreaming.displayName = 'RunCommandStreaming';
|
||||
@@ -0,0 +1,10 @@
|
||||
import { LocalSystemApiName } from '@lobechat/builtin-tool-local-system';
|
||||
|
||||
import { RunCommandStreaming } from './RunCommand';
|
||||
|
||||
/**
|
||||
* Local System Streaming Components Registry
|
||||
*/
|
||||
export const LocalSystemStreamings = {
|
||||
[LocalSystemApiName.runCommand]: RunCommandStreaming,
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system';
|
||||
import { type BuiltinStreaming } from '@lobechat/types';
|
||||
|
||||
import { CodeInterpreterIdentifier } from './code-interpreter';
|
||||
import { CodeInterpreterStreamings } from './code-interpreter/Streaming';
|
||||
import { LocalSystemStreamings } from './local-system/Streaming';
|
||||
|
||||
/**
|
||||
* Builtin tools streaming renderer registry
|
||||
* Organized by toolset (identifier) -> API name
|
||||
*
|
||||
* Streaming components are used to render tool calls while they are
|
||||
* still executing, allowing real-time feedback to users.
|
||||
* The component should fetch streaming content from store internally.
|
||||
*/
|
||||
const BuiltinToolStreamings: Record<string, Record<string, BuiltinStreaming>> = {
|
||||
[CodeInterpreterIdentifier]: CodeInterpreterStreamings as Record<string, BuiltinStreaming>,
|
||||
[LocalSystemManifest.identifier]: LocalSystemStreamings as Record<string, BuiltinStreaming>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get builtin streaming component for a specific API
|
||||
* @param identifier - Tool identifier (e.g., 'lobe-code-interpreter')
|
||||
* @param apiName - API name (e.g., 'executeCode')
|
||||
*/
|
||||
export const getBuiltinStreaming = (
|
||||
identifier?: string,
|
||||
apiName?: string,
|
||||
): BuiltinStreaming | undefined => {
|
||||
if (!identifier || !apiName) return undefined;
|
||||
|
||||
const toolset = BuiltinToolStreamings[identifier];
|
||||
if (!toolset) return undefined;
|
||||
|
||||
return toolset[apiName];
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import { type BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
interface CrawlMultiPagesParams {
|
||||
urls: string[];
|
||||
}
|
||||
|
||||
export const CrawlMultiPagesInspector = memo<BuiltinInspectorProps<CrawlMultiPagesParams>>(
|
||||
({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
// Show count and first domain for context
|
||||
let displayText = '';
|
||||
if (args?.urls && args.urls.length > 0) {
|
||||
const count = args.urls.length;
|
||||
try {
|
||||
const firstUrl = new URL(args.urls[0]);
|
||||
displayText = count > 1 ? `${firstUrl.hostname} +${count - 1}` : firstUrl.hostname;
|
||||
} catch {
|
||||
displayText = `${count} pages`;
|
||||
}
|
||||
}
|
||||
|
||||
// When loading, show "联网搜索 > 读取多个页面内容"
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cx(styles.root, styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-web-browsing.title')}</span>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.crawlMultiPages')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.crawlMultiPages')}</span>
|
||||
{displayText && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{displayText}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CrawlMultiPagesInspector.displayName = 'CrawlMultiPagesInspector';
|
||||
|
||||
export default CrawlMultiPagesInspector;
|
||||
@@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import type { BuiltinInspectorProps } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
interface CrawlSinglePageParams {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const CrawlSinglePageInspector = memo<BuiltinInspectorProps<CrawlSinglePageParams>>(
|
||||
({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
// When loading, show "联网搜索 > 读取页面内容"
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cx(styles.root, styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-web-browsing.title')}</span>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.crawlSinglePage')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.crawlSinglePage')}</span>
|
||||
{args.url && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{args.url}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CrawlSinglePageInspector.displayName = 'CrawlSinglePageInspector';
|
||||
|
||||
export default CrawlSinglePageInspector;
|
||||
@@ -0,0 +1,59 @@
|
||||
'use client';
|
||||
|
||||
import { type BuiltinInspectorProps, type SearchQuery } from '@lobechat/types';
|
||||
import { Icon } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
content: css`
|
||||
font-family: ${token.fontFamilyCode};
|
||||
`,
|
||||
root: css`
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
color: ${token.colorTextDescription};
|
||||
`,
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
export const SearchInspector = memo<BuiltinInspectorProps<SearchQuery>>(({ args, isLoading }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
const query = args?.query || '';
|
||||
|
||||
// When loading, show "联网搜索 > 搜索页面"
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cx(styles.root, styles.shinyText)}>
|
||||
<span>{t('builtins.lobe-web-browsing.title')}</span>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.search')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<span>{t('builtins.lobe-web-browsing.apiName.search')}</span>
|
||||
{query && (
|
||||
<>
|
||||
<Icon icon={ChevronRight} style={{ marginInline: 4 }} />
|
||||
<span className={styles.content}>{query}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SearchInspector.displayName = 'SearchInspector';
|
||||
|
||||
export default SearchInspector;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { WebBrowsingApiName } from '../index';
|
||||
import { CrawlMultiPagesInspector } from './CrawlMultiPages';
|
||||
import { CrawlSinglePageInspector } from './CrawlSinglePage';
|
||||
import { SearchInspector } from './Search';
|
||||
|
||||
/**
|
||||
* Web Browsing Inspector Components Registry
|
||||
*/
|
||||
export const WebBrowsingInspectors = {
|
||||
[WebBrowsingApiName.crawlMultiPages]: CrawlMultiPagesInspector,
|
||||
[WebBrowsingApiName.crawlSinglePage]: CrawlSinglePageInspector,
|
||||
[WebBrowsingApiName.search]: SearchInspector,
|
||||
};
|
||||
Reference in New Issue
Block a user