@lobehub/lobehub 2.0.0-next.24 → 2.0.0-next.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/development/database-schema.dbml +1 -0
  4. package/e2e/src/features/discover/detail-pages.feature +95 -0
  5. package/e2e/src/features/discover/interactions.feature +113 -0
  6. package/e2e/src/steps/discover/detail-pages.steps.ts +295 -0
  7. package/e2e/src/steps/discover/interactions.steps.ts +451 -0
  8. package/package.json +1 -1
  9. package/packages/database/migrations/0043_add_ai_model_settings.sql +1 -0
  10. package/packages/database/migrations/meta/0043_snapshot.json +8419 -0
  11. package/packages/database/migrations/meta/_journal.json +7 -0
  12. package/packages/database/src/core/migrations.json +10 -2
  13. package/packages/database/src/repositories/aiInfra/index.test.ts +198 -0
  14. package/packages/database/src/repositories/aiInfra/index.ts +2 -1
  15. package/packages/database/src/schemas/aiInfra.ts +2 -0
  16. package/src/app/[variants]/(main)/labs/page.tsx +9 -8
  17. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +152 -0
  18. package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +70 -0
  19. package/src/features/Conversation/Messages/Group/Actions/index.tsx +21 -0
  20. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +91 -0
  21. package/src/features/Conversation/Messages/Group/EditState.tsx +51 -0
  22. package/src/features/Conversation/Messages/Group/Error/index.tsx +53 -0
  23. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +73 -0
  24. package/src/features/Conversation/Messages/Group/MessageContent.tsx +39 -0
  25. package/src/features/Conversation/Messages/Group/Tool/Inspector/BuiltinPluginTitle.tsx +49 -0
  26. package/src/features/Conversation/Messages/Group/Tool/Inspector/Debug.tsx +70 -0
  27. package/src/features/Conversation/Messages/Group/Tool/Inspector/PluginResult.tsx +34 -0
  28. package/src/features/Conversation/Messages/Group/Tool/Inspector/PluginState.tsx +18 -0
  29. package/src/features/Conversation/Messages/Group/Tool/Inspector/Settings.tsx +40 -0
  30. package/src/features/Conversation/Messages/Group/Tool/Inspector/ToolTitle.tsx +92 -0
  31. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +176 -0
  32. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/ObjectEntity.tsx +81 -0
  33. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/ValueCell.tsx +43 -0
  34. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/index.tsx +134 -0
  35. package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +88 -0
  36. package/src/features/Conversation/Messages/Group/Tool/Render/ErrorResponse.tsx +35 -0
  37. package/src/features/Conversation/Messages/Group/Tool/Render/LoadingPlaceholder/index.tsx +29 -0
  38. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +66 -0
  39. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +105 -0
  40. package/src/features/Conversation/Messages/Group/Tool/index.tsx +75 -0
  41. package/src/features/Conversation/Messages/Group/Tools.tsx +46 -0
  42. package/src/features/Conversation/Messages/Group/index.tsx +140 -0
  43. package/src/features/Conversation/Messages/index.tsx +12 -0
  44. package/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx +2 -2
  45. package/src/services/chat/contextEngineering.ts +6 -5
  46. package/src/services/message/server.ts +10 -0
  47. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +309 -2
  48. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +2 -22
  49. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +272 -14
@@ -0,0 +1,51 @@
1
+ import { MessageInput } from '@lobehub/ui/chat';
2
+ import { memo, useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+
8
+ export interface EditStateProps {
9
+ content: string;
10
+ id: string;
11
+ }
12
+
13
+ const EditState = memo<EditStateProps>(({ id, content }) => {
14
+ const { t } = useTranslation('common');
15
+
16
+ const text = useMemo(
17
+ () => ({
18
+ cancel: t('cancel'),
19
+ confirm: t('ok'),
20
+ edit: t('edit'),
21
+ }),
22
+ [],
23
+ );
24
+
25
+ const [toggleMessageEditing, updateMessageContent] = useChatStore((s) => [
26
+ s.toggleMessageEditing,
27
+ s.modifyMessageContent,
28
+ ]);
29
+
30
+ const onEditingChange = (value: string) => {
31
+ updateMessageContent(id, value);
32
+ toggleMessageEditing(id, false);
33
+ };
34
+
35
+ return (
36
+ <Flexbox paddingBlock={'0 8px'}>
37
+ <MessageInput
38
+ defaultValue={content ? String(content) : ''}
39
+ editButtonSize={'small'}
40
+ onCancel={() => {
41
+ toggleMessageEditing(id, false);
42
+ }}
43
+ onConfirm={onEditingChange}
44
+ text={text}
45
+ variant={'outlined'}
46
+ />
47
+ </Flexbox>
48
+ );
49
+ });
50
+
51
+ export default EditState;
@@ -0,0 +1,53 @@
1
+ import { ChatMessageError } from '@lobechat/types';
2
+ import { Alert } from '@lobehub/ui';
3
+ import { Button } from 'antd';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import ErrorMessageExtra, { useErrorContent } from '@/features/Conversation/Error';
9
+ import { useChatStore } from '@/store/chat';
10
+
11
+ export interface ErrorContentProps {
12
+ error?: ChatMessageError;
13
+ id: string;
14
+ }
15
+
16
+ const ErrorContent = memo<ErrorContentProps>(({ error, id }) => {
17
+ const { t } = useTranslation('common');
18
+ const errorProps = useErrorContent(error);
19
+
20
+ const [deleteMessage] = useChatStore((s) => [s.deleteMessage]);
21
+ const message = <ErrorMessageExtra block data={{ error, id }} />;
22
+
23
+ if (!error?.message) {
24
+ if (!message) return null;
25
+ return <Flexbox>{message}</Flexbox>;
26
+ }
27
+
28
+ return (
29
+ <Flexbox>
30
+ <Alert
31
+ action={
32
+ <Button
33
+ color={'default'}
34
+ onClick={() => {
35
+ deleteMessage(id);
36
+ }}
37
+ size={'small'}
38
+ variant={'filled'}
39
+ >
40
+ {t('delete')}
41
+ </Button>
42
+ }
43
+ closable={false}
44
+ extra={message}
45
+ showIcon
46
+ type={'error'}
47
+ {...errorProps}
48
+ />
49
+ </Flexbox>
50
+ );
51
+ });
52
+
53
+ export default ErrorContent;
@@ -0,0 +1,73 @@
1
+ import { AssistantContentBlock } from '@lobechat/types';
2
+ import { createStyles } from 'antd-style';
3
+ import { motion } from 'framer-motion';
4
+ import { memo, use } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
8
+ import { useChatStore } from '@/store/chat';
9
+
10
+ import { ContentBlock } from './ContentBlock';
11
+
12
+ const useStyles = createStyles(({ css }) => {
13
+ return {
14
+ container: css`
15
+ &:has(.tool-blocks) {
16
+ width: 100%;
17
+ }
18
+ `,
19
+ };
20
+ });
21
+
22
+ interface GroupChildrenProps {
23
+ blocks: AssistantContentBlock[];
24
+ contentId?: string;
25
+ disableEditing?: boolean;
26
+ messageIndex: number;
27
+ }
28
+
29
+ const GroupChildren = memo<GroupChildrenProps>(
30
+ ({ blocks, contentId, disableEditing, messageIndex }) => {
31
+ const { styles } = useStyles();
32
+
33
+ const [toggleMessageEditing] = useChatStore((s) => [s.toggleMessageEditing]);
34
+ const virtuosoRef = use(VirtuosoContext);
35
+
36
+ return (
37
+ <Flexbox className={styles.container} gap={8}>
38
+ {blocks.map((item, index) => {
39
+ return item.id === contentId ? (
40
+ <Flexbox
41
+ key={index}
42
+ onDoubleClick={(e) => {
43
+ if (disableEditing || item.error || !e.altKey) return;
44
+
45
+ toggleMessageEditing(item.id, true);
46
+ virtuosoRef?.current?.scrollIntoView({
47
+ align: 'start',
48
+ behavior: 'auto',
49
+ index: messageIndex,
50
+ });
51
+ }}
52
+ >
53
+ <ContentBlock index={index} {...item} />
54
+ </Flexbox>
55
+ ) : (
56
+ <motion.div
57
+ animate={{ height: 'auto', opacity: 1 }}
58
+ exit={{ height: 0, opacity: 0 }}
59
+ initial={{ height: 0, opacity: 0 }}
60
+ key={index}
61
+ style={{ overflow: 'hidden' }}
62
+ transition={{ duration: 0.3, ease: 'easeInOut' }}
63
+ >
64
+ <ContentBlock index={index} {...item} />
65
+ </motion.div>
66
+ );
67
+ })}
68
+ </Flexbox>
69
+ );
70
+ },
71
+ );
72
+
73
+ export default GroupChildren;
@@ -0,0 +1,39 @@
1
+ import { Markdown, MarkdownProps } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { memo } from 'react';
4
+
5
+ import BubblesLoading from '@/components/BubblesLoading';
6
+ import { LOADING_FLAT } from '@/const/message';
7
+
8
+ import { normalizeThinkTags, processWithArtifact } from '../../utils/markdown';
9
+
10
+ const useStyles = createStyles(({ css, token }) => {
11
+ return {
12
+ pWithTool: css`
13
+ color: ${token.colorTextTertiary};
14
+ `,
15
+ };
16
+ });
17
+ interface ContentBlockProps {
18
+ content: string;
19
+ hasTools?: boolean;
20
+ markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
21
+ }
22
+
23
+ const MessageContent = memo<ContentBlockProps>(({ content, hasTools, markdownProps }) => {
24
+ const message = normalizeThinkTags(processWithArtifact(content));
25
+
26
+ const { styles, cx } = useStyles();
27
+
28
+ if (content === LOADING_FLAT) return <BubblesLoading />;
29
+
30
+ return (
31
+ content && (
32
+ <Markdown {...markdownProps} className={cx(hasTools && styles.pWithTool)} variant={'chat'}>
33
+ {message}
34
+ </Markdown>
35
+ )
36
+ );
37
+ });
38
+
39
+ export default MessageContent;
@@ -0,0 +1,49 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { ChevronRight } from 'lucide-react';
4
+ import { ReactNode, memo } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { shinyTextStylish } from '@/styles/loading';
8
+
9
+ export const useStyles = createStyles(({ css, token }) => ({
10
+ apiName: css`
11
+ overflow: hidden;
12
+ display: -webkit-box;
13
+ -webkit-box-orient: vertical;
14
+ -webkit-line-clamp: 1;
15
+
16
+ font-family: ${token.fontFamilyCode};
17
+ font-size: 12px;
18
+ text-overflow: ellipsis;
19
+ `,
20
+
21
+ shinyText: shinyTextStylish(token),
22
+ }));
23
+
24
+ interface BuiltinPluginTitleProps {
25
+ apiName: string;
26
+ hasResult?: boolean;
27
+ icon?: ReactNode;
28
+ identifier: string;
29
+ index: number;
30
+ messageId: string;
31
+ title: string;
32
+ toolCallId: string;
33
+ }
34
+
35
+ const BuiltinPluginTitle = memo<BuiltinPluginTitleProps>(({ apiName, title, hasResult }) => {
36
+ const { styles } = useStyles();
37
+
38
+ const isLoading = !hasResult;
39
+
40
+ return (
41
+ <Flexbox align={'center'} className={isLoading ? styles.shinyText : ''} gap={4} horizontal>
42
+ <div>{title}</div>
43
+ <Icon icon={ChevronRight} />
44
+ <span className={styles.apiName}>{apiName}</span>
45
+ </Flexbox>
46
+ );
47
+ });
48
+
49
+ export default BuiltinPluginTitle;
@@ -0,0 +1,70 @@
1
+ import { Highlighter } from '@lobehub/ui';
2
+ import { Tabs } from 'antd';
3
+ import { memo, useMemo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import PluginResult from './PluginResult';
7
+ import PluginState from './PluginState';
8
+
9
+ interface DebugProps {
10
+ apiName: string;
11
+ identifier: string;
12
+ requestArgs?: string;
13
+ result?: { content: string | null; error?: any; state?: any };
14
+ toolCallId: string;
15
+ type?: string;
16
+ }
17
+
18
+ const Debug = memo<DebugProps>(({ result, requestArgs, toolCallId, apiName, identifier, type }) => {
19
+ const { t } = useTranslation('plugin');
20
+
21
+ const params = useMemo(() => {
22
+ try {
23
+ return JSON.stringify(JSON.parse(requestArgs || ''), null, 2);
24
+ } catch {
25
+ return '';
26
+ }
27
+ }, [requestArgs]);
28
+
29
+ const functionCall = useMemo(() => {
30
+ return {
31
+ apiName,
32
+ arguments: requestArgs,
33
+ id: toolCallId,
34
+ identifier,
35
+ type,
36
+ };
37
+ }, [requestArgs, toolCallId, apiName, identifier, type]);
38
+
39
+ return (
40
+ <Tabs
41
+ items={[
42
+ {
43
+ children: <Highlighter language={'json'}>{params}</Highlighter>,
44
+ key: 'arguments',
45
+ label: t('debug.arguments'),
46
+ },
47
+ {
48
+ children: <PluginResult content={result?.content} />,
49
+ key: 'response',
50
+ label: t('debug.response'),
51
+ },
52
+ {
53
+ children: (
54
+ <Highlighter language={'json'}>{JSON.stringify(functionCall, null, 2)}</Highlighter>
55
+ ),
56
+ key: 'function_call',
57
+ label: t('debug.function_call'),
58
+ },
59
+ {
60
+ children: <PluginState state={result?.state} />,
61
+ key: 'pluginState',
62
+ label: t('debug.pluginState'),
63
+ },
64
+ ]}
65
+ style={{ display: 'grid', maxWidth: 800, minWidth: 400 }}
66
+ />
67
+ );
68
+ });
69
+
70
+ export default Debug;
@@ -0,0 +1,34 @@
1
+ import { Highlighter } from '@lobehub/ui';
2
+ import { memo, useMemo } from 'react';
3
+
4
+ export interface PluginResultProps {
5
+ content?: string | null;
6
+ variant?: 'filled' | 'outlined' | 'borderless';
7
+ }
8
+
9
+ const PluginResult = memo<PluginResultProps>(({ content, variant }) => {
10
+ const { data, language } = useMemo(() => {
11
+ try {
12
+ const parsed = JSON.parse(content || '');
13
+ // Special case: if the parsed result is a string, it means the original content was a stringified string
14
+ if (typeof parsed === 'string') {
15
+ return { data: parsed, language: 'plaintext' }; // Return the parsed string directly, do not re-serialize
16
+ }
17
+ return { data: JSON.stringify(parsed, null, 2), language: 'json' };
18
+ } catch {
19
+ return { data: content || '', language: 'plaintext' };
20
+ }
21
+ }, [content]);
22
+
23
+ return (
24
+ <Highlighter
25
+ language={language}
26
+ style={{ maxHeight: 200, overflow: 'scroll', width: '100%' }}
27
+ variant={variant}
28
+ >
29
+ {data}
30
+ </Highlighter>
31
+ );
32
+ });
33
+
34
+ export default PluginResult;
@@ -0,0 +1,18 @@
1
+ import { Highlighter } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+
4
+ export interface PluginStateProps {
5
+ state?: any;
6
+ }
7
+
8
+ const PluginState = memo<PluginStateProps>(({ state }) => {
9
+ if (!state) return null;
10
+
11
+ return (
12
+ <Highlighter language={'json'} style={{ maxHeight: 200, maxWidth: 800, overflow: 'scroll' }}>
13
+ {JSON.stringify(state, null, 2)}
14
+ </Highlighter>
15
+ );
16
+ });
17
+
18
+ export default PluginState;
@@ -0,0 +1,40 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { LucideSettings } from 'lucide-react';
3
+ import { memo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import PluginDetailModal from '@/features/PluginDetailModal';
7
+ import { pluginHelpers, useToolStore } from '@/store/tool';
8
+ import { pluginSelectors } from '@/store/tool/selectors';
9
+
10
+ const Settings = memo<{ id: string }>(({ id }) => {
11
+ const item = useToolStore(pluginSelectors.getToolManifestById(id));
12
+ const [open, setOpen] = useState(false);
13
+ const { t } = useTranslation('plugin');
14
+ const hasSettings = pluginHelpers.isSettingSchemaNonEmpty(item?.settings);
15
+
16
+ return (
17
+ hasSettings && (
18
+ <>
19
+ <ActionIcon
20
+ icon={LucideSettings}
21
+ onClick={() => {
22
+ setOpen(true);
23
+ }}
24
+ size={'small'}
25
+ title={t('setting')}
26
+ />
27
+ <PluginDetailModal
28
+ id={id}
29
+ onClose={() => {
30
+ setOpen(false);
31
+ }}
32
+ open={open}
33
+ schema={item?.settings}
34
+ />
35
+ </>
36
+ )
37
+ );
38
+ });
39
+
40
+ export default Settings;
@@ -0,0 +1,92 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { ChevronRight } from 'lucide-react';
5
+ import { memo, useMemo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { pluginHelpers, useToolStore } from '@/store/tool';
10
+ import { toolSelectors } from '@/store/tool/selectors';
11
+ import { shinyTextStylish } from '@/styles/loading';
12
+ import { LocalSystemManifest } from '@/tools/local-system';
13
+ import { WebBrowsingManifest } from '@/tools/web-browsing';
14
+
15
+ import BuiltinPluginTitle from './BuiltinPluginTitle';
16
+
17
+ export const useStyles = createStyles(({ css, token }) => ({
18
+ apiName: css`
19
+ overflow: hidden;
20
+ display: -webkit-box;
21
+ -webkit-box-orient: vertical;
22
+ -webkit-line-clamp: 1;
23
+
24
+ font-family: ${token.fontFamilyCode};
25
+ font-size: 12px;
26
+ text-overflow: ellipsis;
27
+ `,
28
+
29
+ shinyText: shinyTextStylish(token),
30
+ }));
31
+
32
+ interface ToolTitleProps {
33
+ apiName: string;
34
+ hasResult?: boolean;
35
+ identifier: string;
36
+ index: number;
37
+ messageId: string;
38
+ toolCallId: string;
39
+ }
40
+
41
+ const ToolTitle = memo<ToolTitleProps>(
42
+ ({ identifier, apiName, hasResult, index, toolCallId, messageId }) => {
43
+ const { t } = useTranslation('plugin');
44
+ const { styles } = useStyles();
45
+
46
+ const isLoading = !hasResult;
47
+
48
+ const pluginMeta = useToolStore(toolSelectors.getMetaById(identifier), isEqual);
49
+
50
+ const plugins = useMemo(
51
+ () => [
52
+ {
53
+ apiName: t(`search.apiName.${apiName}`, apiName),
54
+ id: WebBrowsingManifest.identifier,
55
+ title: t('search.title'),
56
+ },
57
+ {
58
+ apiName: t(`localSystem.apiName.${apiName}`, apiName),
59
+ id: LocalSystemManifest.identifier,
60
+ title: t('localSystem.title'),
61
+ },
62
+ ],
63
+ [],
64
+ );
65
+
66
+ const builtinPluginTitle = plugins.find((item) => item.id === identifier);
67
+
68
+ if (!!builtinPluginTitle) {
69
+ return (
70
+ <BuiltinPluginTitle
71
+ {...builtinPluginTitle}
72
+ hasResult={hasResult}
73
+ identifier={identifier}
74
+ index={index}
75
+ messageId={messageId}
76
+ toolCallId={toolCallId}
77
+ />
78
+ );
79
+ }
80
+
81
+ const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin');
82
+
83
+ return (
84
+ <Flexbox align={'center'} className={isLoading ? styles.shinyText : ''} gap={6} horizontal>
85
+ <div>{pluginTitle}</div> <Icon icon={ChevronRight} />
86
+ <span className={styles.apiName}>{apiName}</span>
87
+ </Flexbox>
88
+ );
89
+ },
90
+ );
91
+
92
+ export default ToolTitle;
@@ -0,0 +1,176 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { Check, LayoutPanelTop, LogsIcon, LucideBug, LucideBugOff, X } from 'lucide-react';
4
+ import { CSSProperties, memo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { LOADING_FLAT } from '@/const/message';
9
+ import { shinyTextStylish } from '@/styles/loading';
10
+
11
+ import Debug from './Debug';
12
+ import Settings from './Settings';
13
+ import ToolTitle from './ToolTitle';
14
+
15
+ export const useStyles = createStyles(({ css, token, cx }) => ({
16
+ actions: cx(
17
+ 'inspector-container',
18
+ css`
19
+ opacity: 0;
20
+ transition: opacity 300ms ease-in-out;
21
+ `,
22
+ ),
23
+ apiName: css`
24
+ overflow: hidden;
25
+ display: -webkit-box;
26
+ -webkit-box-orient: vertical;
27
+ -webkit-line-clamp: 1;
28
+
29
+ font-family: ${token.fontFamilyCode};
30
+ font-size: 12px;
31
+ text-overflow: ellipsis;
32
+ `,
33
+ container: css`
34
+ :hover {
35
+ .inspector-container {
36
+ opacity: 1;
37
+ }
38
+ }
39
+ `,
40
+ plugin: css`
41
+ display: flex;
42
+ gap: 4px;
43
+ align-items: center;
44
+ width: fit-content;
45
+ `,
46
+ shinyText: shinyTextStylish(token),
47
+ tool: css`
48
+ cursor: pointer;
49
+
50
+ width: fit-content;
51
+ padding-block: 2px;
52
+ border-radius: 6px;
53
+
54
+ color: ${token.colorTextTertiary};
55
+
56
+ &:hover {
57
+ background: ${token.colorFillTertiary};
58
+ }
59
+ `,
60
+ }));
61
+
62
+ interface InspectorProps {
63
+ apiName: string;
64
+ arguments?: string;
65
+ hidePluginUI?: boolean;
66
+ id: string;
67
+ identifier: string;
68
+ index: number;
69
+ messageId: string;
70
+ result?: { content: string | null; error?: any; state?: any };
71
+ setShowPluginRender: (show: boolean) => void;
72
+ setShowRender: (show: boolean) => void;
73
+ showPluginRender: boolean;
74
+ showPortal?: boolean;
75
+ showRender: boolean;
76
+ style?: CSSProperties;
77
+ type?: string;
78
+ }
79
+
80
+ const Inspectors = memo<InspectorProps>(
81
+ ({
82
+ messageId,
83
+ index,
84
+ identifier,
85
+ apiName,
86
+ id,
87
+ arguments: requestArgs,
88
+ showRender,
89
+ result,
90
+ setShowRender,
91
+ showPluginRender,
92
+ setShowPluginRender,
93
+ hidePluginUI = false,
94
+ type,
95
+ }) => {
96
+ const { t } = useTranslation('plugin');
97
+ const { styles, theme } = useStyles();
98
+
99
+ const [showDebug, setShowDebug] = useState(false);
100
+
101
+ const hasError = !!result?.error;
102
+ const hasSuccessResult = !!result?.content && result.content !== LOADING_FLAT;
103
+
104
+ const hasResult = hasSuccessResult || hasError;
105
+
106
+ return (
107
+ <Flexbox className={styles.container} gap={4}>
108
+ <Flexbox align={'center'} distribution={'space-between'} gap={8} horizontal>
109
+ <Flexbox
110
+ align={'center'}
111
+ className={styles.tool}
112
+ gap={8}
113
+ horizontal
114
+ onClick={() => {
115
+ setShowRender(!showRender);
116
+ }}
117
+ paddingInline={4}
118
+ >
119
+ <ToolTitle
120
+ apiName={apiName}
121
+ hasResult={hasResult}
122
+ identifier={identifier}
123
+ index={index}
124
+ messageId={messageId}
125
+ toolCallId={id}
126
+ />
127
+ </Flexbox>
128
+ <Flexbox align={'center'} gap={8} horizontal>
129
+ <Flexbox className={styles.actions} horizontal>
130
+ {showRender && !hidePluginUI && (
131
+ <ActionIcon
132
+ icon={showPluginRender ? LogsIcon : LayoutPanelTop}
133
+ onClick={() => {
134
+ setShowPluginRender(!showPluginRender);
135
+ }}
136
+ size={'small'}
137
+ title={showPluginRender ? t('inspector.args') : t('inspector.pluginRender')}
138
+ />
139
+ )}
140
+ <ActionIcon
141
+ icon={showDebug ? LucideBugOff : LucideBug}
142
+ onClick={() => {
143
+ setShowDebug(!showDebug);
144
+ }}
145
+ size={'small'}
146
+ title={t(showDebug ? 'debug.off' : 'debug.on')}
147
+ />
148
+ <Settings id={identifier} />
149
+ </Flexbox>
150
+ {hasResult && (
151
+ <Flexbox align={'center'} gap={4} horizontal style={{ fontSize: 12 }}>
152
+ {hasError ? (
153
+ <X color={theme.colorError} size={14} />
154
+ ) : (
155
+ <Check color={theme.colorSuccess} size={14} />
156
+ )}
157
+ </Flexbox>
158
+ )}
159
+ </Flexbox>
160
+ </Flexbox>
161
+ {showDebug && (
162
+ <Debug
163
+ apiName={apiName}
164
+ identifier={identifier}
165
+ requestArgs={requestArgs}
166
+ result={result}
167
+ toolCallId={id}
168
+ type={type}
169
+ />
170
+ )}
171
+ </Flexbox>
172
+ );
173
+ },
174
+ );
175
+
176
+ export default Inspectors;