@lobehub/chat 1.51.2 → 1.51.3

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 (59) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/Dockerfile +1 -1
  3. package/Dockerfile.database +1 -1
  4. package/changelog/v1.json +12 -0
  5. package/docs/usage/providers/wenxin.mdx +16 -13
  6. package/docs/usage/providers/wenxin.zh-CN.mdx +11 -8
  7. package/package.json +1 -2
  8. package/src/app/(main)/settings/llm/ProviderList/providers.tsx +2 -4
  9. package/src/config/aiModels/wenxin.ts +125 -19
  10. package/src/config/llm.ts +3 -5
  11. package/src/config/modelProviders/wenxin.ts +100 -23
  12. package/src/const/auth.ts +0 -3
  13. package/src/features/Conversation/Error/APIKeyForm/index.tsx +0 -3
  14. package/src/features/Conversation/components/ChatItem/utils.test.ts +284 -0
  15. package/src/features/Conversation/components/ChatItem/utils.ts +39 -8
  16. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/rehypePlugin.test.ts +125 -0
  17. package/src/features/DevPanel/CacheViewer/DataTable/index.tsx +33 -0
  18. package/src/features/DevPanel/CacheViewer/cacheProvider.tsx +64 -0
  19. package/src/features/DevPanel/CacheViewer/getCacheEntries.ts +52 -0
  20. package/src/features/DevPanel/CacheViewer/index.tsx +25 -0
  21. package/src/features/DevPanel/CacheViewer/schema.ts +49 -0
  22. package/src/features/DevPanel/FeatureFlagViewer/Form.tsx +93 -0
  23. package/src/features/DevPanel/FeatureFlagViewer/index.tsx +11 -0
  24. package/src/features/DevPanel/MetadataViewer/Ld.tsx +25 -0
  25. package/src/features/DevPanel/MetadataViewer/MetaData.tsx +30 -0
  26. package/src/features/DevPanel/MetadataViewer/Og.tsx +75 -0
  27. package/src/features/DevPanel/MetadataViewer/index.tsx +80 -0
  28. package/src/features/DevPanel/MetadataViewer/useHead.ts +16 -0
  29. package/src/features/DevPanel/PostgresViewer/DataTable/index.tsx +39 -49
  30. package/src/features/DevPanel/PostgresViewer/{TableColumns.tsx → SchemaSidebar/Columns.tsx} +6 -4
  31. package/src/features/DevPanel/PostgresViewer/{Schema.tsx → SchemaSidebar/index.tsx} +49 -55
  32. package/src/features/DevPanel/PostgresViewer/index.tsx +4 -2
  33. package/src/features/DevPanel/features/FloatPanel.tsx +218 -0
  34. package/src/features/DevPanel/features/Header.tsx +50 -0
  35. package/src/features/DevPanel/features/Table/TableCell.tsx +73 -0
  36. package/src/features/DevPanel/features/Table/TooltipContent.tsx +39 -0
  37. package/src/features/DevPanel/{PostgresViewer/DataTable/Table.tsx → features/Table/index.tsx} +12 -14
  38. package/src/features/DevPanel/index.tsx +29 -5
  39. package/src/libs/agent-runtime/AgentRuntime.test.ts +0 -1
  40. package/src/libs/agent-runtime/AgentRuntime.ts +7 -0
  41. package/src/libs/agent-runtime/wenxin/index.ts +10 -107
  42. package/src/locales/default/modelProvider.ts +0 -20
  43. package/src/server/modules/AgentRuntime/index.test.ts +0 -21
  44. package/src/services/_auth.ts +0 -14
  45. package/src/store/chat/slices/portal/selectors.test.ts +169 -3
  46. package/src/store/chat/slices/portal/selectors.ts +6 -1
  47. package/src/store/user/slices/modelList/selectors/keyVaults.ts +0 -2
  48. package/src/types/aiProvider.ts +0 -1
  49. package/src/types/user/settings/keyVaults.ts +1 -6
  50. package/src/app/(backend)/webapi/chat/wenxin/route.test.ts +0 -27
  51. package/src/app/(backend)/webapi/chat/wenxin/route.ts +0 -30
  52. package/src/app/(main)/settings/llm/ProviderList/Wenxin/index.tsx +0 -44
  53. package/src/app/(main)/settings/provider/(detail)/wenxin/page.tsx +0 -61
  54. package/src/features/Conversation/Error/APIKeyForm/Wenxin.tsx +0 -49
  55. package/src/features/DevPanel/FloatPanel.tsx +0 -136
  56. package/src/features/DevPanel/PostgresViewer/DataTable/TableCell.tsx +0 -34
  57. package/src/libs/agent-runtime/utils/streams/wenxin.test.ts +0 -153
  58. package/src/libs/agent-runtime/utils/streams/wenxin.ts +0 -38
  59. package/src/libs/agent-runtime/wenxin/type.ts +0 -84
@@ -1,42 +1,19 @@
1
- import { ActionIcon, Icon } from '@lobehub/ui';
2
- import { Button } from 'antd';
1
+ import { Empty } from 'antd';
3
2
  import { createStyles } from 'antd-style';
4
3
  import { Download, Filter, RefreshCw } from 'lucide-react';
5
4
  import React from 'react';
5
+ import { Center, Flexbox } from 'react-layout-kit';
6
6
  import { mutate } from 'swr';
7
7
 
8
- import { FETCH_TABLE_DATA_KEY } from '../usePgTable';
9
- import Table from './Table';
8
+ import Header from '../../features/Header';
9
+ import Table from '../../features/Table';
10
+ import { FETCH_TABLE_DATA_KEY, usePgTable, useTableColumns } from '../usePgTable';
10
11
 
11
12
  const useStyles = createStyles(({ token, css }) => ({
12
13
  dataPanel: css`
13
14
  overflow: hidden;
14
- display: flex;
15
- flex: 1;
16
- flex-direction: column;
17
-
18
- height: 100%;
19
-
20
15
  background: ${token.colorBgContainer};
21
16
  `,
22
- toolbar: css`
23
- display: flex;
24
- align-items: center;
25
- justify-content: space-between;
26
-
27
- padding-block: 12px;
28
- padding-inline: 16px;
29
- border-block-end: 1px solid ${token.colorBorderSecondary};
30
- `,
31
- toolbarButtons: css`
32
- display: flex;
33
- gap: 4px;
34
- `,
35
- toolbarTitle: css`
36
- font-size: ${token.fontSizeLG}px;
37
- font-weight: ${token.fontWeightStrong};
38
- color: ${token.colorText};
39
- `,
40
17
  }));
41
18
 
42
19
  interface DataTableProps {
@@ -46,29 +23,42 @@ interface DataTableProps {
46
23
  const DataTable = ({ tableName }: DataTableProps) => {
47
24
  const { styles } = useStyles();
48
25
 
26
+ const tableColumns = useTableColumns(tableName);
27
+ const tableData = usePgTable(tableName);
28
+ const columns = tableColumns.data?.map((t) => t.name) || [];
29
+ const isLoading = tableColumns.isLoading || tableData.isLoading;
30
+ const dataSource = tableData.data?.data || [];
31
+
49
32
  return (
50
- <div className={styles.dataPanel}>
51
- {/* Toolbar */}
52
- <div className={styles.toolbar}>
53
- <div className={styles.toolbarTitle}>{tableName || 'Select a table'}</div>
54
- <div className={styles.toolbarButtons}>
55
- <Button color={'default'} icon={<Icon icon={Filter} />} variant={'filled'}>
56
- Filter
57
- </Button>
58
- <ActionIcon icon={Download} title={'Export'} />
59
- <ActionIcon
60
- icon={RefreshCw}
61
- onClick={async () => {
33
+ <Flexbox className={styles.dataPanel} flex={1} height={'100%'}>
34
+ <Header
35
+ actions={[
36
+ {
37
+ icon: Filter,
38
+ title: 'Filter',
39
+ },
40
+ {
41
+ icon: Download,
42
+ title: 'Export',
43
+ },
44
+ {
45
+ icon: RefreshCw,
46
+ onClick: async () => {
62
47
  await mutate(FETCH_TABLE_DATA_KEY(tableName));
63
- }}
64
- title={'Refresh'}
65
- />
66
- </div>
67
- </div>
68
-
69
- {/* Table */}
70
- <Table tableName={tableName} />
71
- </div>
48
+ },
49
+ title: 'Refresh',
50
+ },
51
+ ]}
52
+ title={tableName || 'Select a table'}
53
+ />
54
+ {tableName ? (
55
+ <Table columns={columns} dataSource={dataSource} loading={isLoading} />
56
+ ) : (
57
+ <Center height={'80%'}>
58
+ <Empty description={'Select a table to view data'} image={Empty.PRESENTED_IMAGE_SIMPLE} />
59
+ </Center>
60
+ )}
61
+ </Flexbox>
72
62
  );
73
63
  };
74
64
 
@@ -3,7 +3,7 @@ import { createStyles } from 'antd-style';
3
3
  import React from 'react';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
- import { useTableColumns } from './usePgTable';
6
+ import { useTableColumns } from '../usePgTable';
7
7
 
8
8
  const useStyles = createStyles(({ token, css }) => ({
9
9
  container: css`
@@ -26,7 +26,7 @@ interface TableColumnsProps {
26
26
  tableName: string;
27
27
  }
28
28
 
29
- const TableColumns = ({ tableName }: TableColumnsProps) => {
29
+ const Columns = ({ tableName }: TableColumnsProps) => {
30
30
  const { styles } = useStyles();
31
31
 
32
32
  const { data, isLoading } = useTableColumns(tableName);
@@ -34,7 +34,9 @@ const TableColumns = ({ tableName }: TableColumnsProps) => {
34
34
  return (
35
35
  <div className={styles.container}>
36
36
  {isLoading ? (
37
- <div>Loading...</div>
37
+ ` <Center width={'100%'}>
38
+ <Icon icon={Loader2Icon} spin />
39
+ </Center>`
38
40
  ) : (
39
41
  <Flexbox>
40
42
  {data?.map((column) => (
@@ -64,4 +66,4 @@ const TableColumns = ({ tableName }: TableColumnsProps) => {
64
66
  );
65
67
  };
66
68
 
67
- export default TableColumns;
69
+ export default Columns;
@@ -1,11 +1,11 @@
1
- import { Icon } from '@lobehub/ui';
1
+ import { DraggablePanel, DraggablePanelBody, Icon } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
- import { ChevronDown, ChevronRight, Database, Table as TableIcon } from 'lucide-react';
3
+ import { ChevronDown, ChevronRight, Database, Loader2Icon, Table as TableIcon } from 'lucide-react';
4
4
  import React, { useState } from 'react';
5
- import { Flexbox } from 'react-layout-kit';
5
+ import { Center, Flexbox } from 'react-layout-kit';
6
6
 
7
- import TableColumns from './TableColumns';
8
- import { useFetchTables } from './usePgTable';
7
+ import { useFetchTables } from '../usePgTable';
8
+ import Columns from './Columns';
9
9
 
10
10
  const useStyles = createStyles(({ token, css }) => ({
11
11
  button: css`
@@ -51,24 +51,7 @@ const useStyles = createStyles(({ token, css }) => ({
51
51
  color: ${token.colorTextTertiary};
52
52
  `,
53
53
  schemaHeader: css`
54
- display: flex;
55
- gap: 8px;
56
- align-items: center;
57
-
58
- padding-block: 12px;
59
- padding-inline: 16px;
60
-
61
54
  font-weight: ${token.fontWeightStrong};
62
- color: ${token.colorText};
63
- `,
64
- schemaPanel: css`
65
- overflow: scroll;
66
-
67
- width: 280px;
68
- height: 100%;
69
- border-inline-end: 1px solid ${token.colorBorderSecondary};
70
-
71
- background: ${token.colorBgContainer};
72
55
  `,
73
56
  selected: css`
74
57
  background: ${token.colorFillSecondary};
@@ -146,41 +129,52 @@ const SchemaPanel = ({ onTableSelect, selectedTable }: SchemaPanelProps) => {
146
129
  };
147
130
 
148
131
  return (
149
- <div className={styles.schemaPanel}>
150
- <div className={styles.schemaHeader}>
151
- <Database size={16} />
152
- <Flexbox align={'center'} horizontal justify={'space-between'}>
153
- <span>Tables {data?.length}</span>
154
- <span className={styles.schema}>public</span>
132
+ <DraggablePanel placement={'left'}>
133
+ <Flexbox height={'100%'} style={{ overflow: 'hidden', position: 'relative' }}>
134
+ <Flexbox
135
+ align={'center'}
136
+ className={styles.schemaHeader}
137
+ gap={8}
138
+ horizontal
139
+ paddingBlock={12}
140
+ paddingInline={16}
141
+ >
142
+ <Database size={16} />
143
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
144
+ <span>Tables {data?.length}</span>
145
+ <span className={styles.schema}>public</span>
146
+ </Flexbox>
155
147
  </Flexbox>
156
- </div>
157
- {isLoading ? (
158
- <div>Loading...</div>
159
- ) : (
160
- <Flexbox>
161
- {data?.map((table) => (
162
- <div key={table.name}>
163
- <Flexbox
164
- className={cx(styles.tableItem, selectedTable === table.name && styles.selected)}
165
- horizontal
166
- onClick={() => {
167
- toggleTable(table.name);
168
- onTableSelect(table.name);
169
- }}
170
- >
171
- <Icon icon={expandedTables.has(table.name) ? ChevronDown : ChevronRight} />
172
- <TableIcon size={16} />
173
- <Flexbox align={'center'} horizontal justify={'space-between'}>
174
- <span>{table.name}</span>
175
- <span className={styles.count}>{table.count}</span>
148
+ <DraggablePanelBody style={{ padding: 0 }}>
149
+ {isLoading ? (
150
+ <Center height={'100%'} width={'100%'}>
151
+ <Icon icon={Loader2Icon} spin />
152
+ </Center>
153
+ ) : (
154
+ data?.map((table) => (
155
+ <div key={table.name}>
156
+ <Flexbox
157
+ className={cx(styles.tableItem, selectedTable === table.name && styles.selected)}
158
+ horizontal
159
+ onClick={() => {
160
+ toggleTable(table.name);
161
+ onTableSelect(table.name);
162
+ }}
163
+ >
164
+ <Icon icon={expandedTables.has(table.name) ? ChevronDown : ChevronRight} />
165
+ <TableIcon size={16} />
166
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
167
+ <span>{table.name}</span>
168
+ <span className={styles.count}>{table.count}</span>
169
+ </Flexbox>
176
170
  </Flexbox>
177
- </Flexbox>
178
- {expandedTables.has(table.name) && <TableColumns tableName={table.name} />}
179
- </div>
180
- ))}
181
- </Flexbox>
182
- )}
183
- </div>
171
+ {expandedTables.has(table.name) && <Columns tableName={table.name} />}
172
+ </div>
173
+ ))
174
+ )}
175
+ </DraggablePanelBody>
176
+ </Flexbox>
177
+ </DraggablePanel>
184
178
  );
185
179
  };
186
180
 
@@ -1,8 +1,10 @@
1
+ 'use client';
2
+
1
3
  import React, { useState } from 'react';
2
4
  import { Flexbox } from 'react-layout-kit';
3
5
 
4
6
  import DataTable from './DataTable';
5
- import SchemaPanel from './Schema';
7
+ import SchemaSidebar from './SchemaSidebar';
6
8
 
7
9
  // Main Database Panel Component
8
10
  const DatabasePanel = () => {
@@ -10,7 +12,7 @@ const DatabasePanel = () => {
10
12
 
11
13
  return (
12
14
  <Flexbox height={'100%'} horizontal>
13
- <SchemaPanel onTableSelect={setSelectedTable} selectedTable={selectedTable} />
15
+ <SchemaSidebar onTableSelect={setSelectedTable} selectedTable={selectedTable} />
14
16
  <DataTable tableName={selectedTable} />
15
17
  </Flexbox>
16
18
  );
@@ -0,0 +1,218 @@
1
+ 'use client';
2
+
3
+ import { ActionIcon, FluentEmoji, Icon, SideNav } from '@lobehub/ui';
4
+ import { Dropdown, FloatButton } from 'antd';
5
+ import { createStyles } from 'antd-style';
6
+ import { BugIcon, BugOff, XIcon } from 'lucide-react';
7
+ import { ReactNode, memo, useEffect, useState } from 'react';
8
+ import { Flexbox } from 'react-layout-kit';
9
+ import { Rnd } from 'react-rnd';
10
+
11
+ import { BRANDING_NAME } from '@/const/branding';
12
+
13
+ // 定义样式
14
+ const useStyles = createStyles(({ token, css, prefixCls }) => {
15
+ return {
16
+ collapsed: css`
17
+ pointer-events: none;
18
+ transform: scale(0.8);
19
+ opacity: 0;
20
+ `,
21
+ expanded: css`
22
+ pointer-events: auto;
23
+ transform: scale(1);
24
+ opacity: 1;
25
+ `,
26
+ floatButton: css`
27
+ inset-block-end: 16px;
28
+ inset-inline-end: 16px;
29
+
30
+ width: 36px;
31
+ height: 36px;
32
+ border: 1px solid ${token.colorBorderSecondary};
33
+
34
+ font-size: 20px;
35
+ .${prefixCls}-float-btn-body {
36
+ background: ${token.colorBgLayout};
37
+
38
+ &:hover {
39
+ width: auto;
40
+ background: ${token.colorBgElevated};
41
+ }
42
+ }
43
+ `,
44
+ header: css`
45
+ cursor: move;
46
+ user-select: none;
47
+
48
+ padding-block: 8px;
49
+ padding-inline: 16px;
50
+ border-block-end: 1px solid ${token.colorBorderSecondary};
51
+
52
+ color: ${token.colorText};
53
+
54
+ background: ${token.colorFillAlter};
55
+ `,
56
+ panel: css`
57
+ position: fixed;
58
+ z-index: 1000;
59
+
60
+ overflow: hidden;
61
+ display: flex;
62
+
63
+ border: 1px solid ${token.colorBorderSecondary};
64
+ border-radius: 12px;
65
+
66
+ background: ${token.colorBgContainer};
67
+ box-shadow: ${token.boxShadow};
68
+
69
+ transition: opacity ${token.motionDurationMid} ${token.motionEaseInOut};
70
+ `,
71
+ };
72
+ });
73
+
74
+ const minWidth = 800;
75
+ const minHeight = 600;
76
+
77
+ interface CollapsibleFloatPanelProps {
78
+ items: { children: ReactNode; icon: ReactNode; key: string }[];
79
+ }
80
+
81
+ const CollapsibleFloatPanel = memo<CollapsibleFloatPanelProps>(({ items }) => {
82
+ const { styles, theme } = useStyles();
83
+ const [tab, setTab] = useState<string>(items[0].key);
84
+ const [isHide, setIsHide] = useState(false);
85
+ const [isExpanded, setIsExpanded] = useState(false);
86
+ const [position, setPosition] = useState({ x: 100, y: 100 });
87
+ const [size, setSize] = useState({ height: minHeight, width: minWidth });
88
+
89
+ useEffect(() => {
90
+ try {
91
+ const localStoragePosition = localStorage.getItem('debug-panel-position');
92
+ if (localStoragePosition && JSON.parse(localStoragePosition)) {
93
+ setPosition(JSON.parse(localStoragePosition));
94
+ }
95
+ } catch {
96
+ /* empty */
97
+ }
98
+
99
+ try {
100
+ const localStorageSize = localStorage.getItem('debug-panel-size');
101
+ if (localStorageSize && JSON.parse(localStorageSize)) {
102
+ setSize(JSON.parse(localStorageSize));
103
+ }
104
+ } catch {
105
+ /* empty */
106
+ }
107
+ }, []);
108
+
109
+ return (
110
+ <>
111
+ {!isHide && (
112
+ <Dropdown
113
+ menu={{
114
+ items: [
115
+ {
116
+ icon: (
117
+ <Icon color={theme.colorTextSecondary} icon={BugOff} size={{ fontSize: 16 }} />
118
+ ),
119
+ key: 'hide',
120
+ label: 'Hide Toolbar',
121
+ onClick: () => setIsHide(true),
122
+ },
123
+ ],
124
+ }}
125
+ trigger={['hover']}
126
+ >
127
+ <FloatButton
128
+ className={styles.floatButton}
129
+ icon={<Icon icon={isExpanded ? BugOff : BugIcon} />}
130
+ onClick={() => setIsExpanded(!isExpanded)}
131
+ />
132
+ </Dropdown>
133
+ )}
134
+ {isExpanded && (
135
+ <Rnd
136
+ bounds="window"
137
+ className={`${styles.panel} ${isExpanded ? styles.expanded : styles.collapsed}`}
138
+ dragHandleClassName="panel-drag-handle"
139
+ minHeight={minHeight}
140
+ minWidth={minWidth}
141
+ onDragStop={(e, d) => {
142
+ setPosition({ x: d.x, y: d.y });
143
+ }}
144
+ onResizeStop={(e, direction, ref, delta, position) => {
145
+ setSize({
146
+ height: Number(ref.style.height),
147
+ width: Number(ref.style.width),
148
+ });
149
+ setPosition(position);
150
+ }}
151
+ position={position}
152
+ size={size}
153
+ >
154
+ <Flexbox
155
+ height={'100%'}
156
+ horizontal
157
+ style={{ overflow: 'hidden', position: 'relative' }}
158
+ width={'100%'}
159
+ >
160
+ <SideNav
161
+ avatar={<FluentEmoji emoji={'🧰'} size={24} />}
162
+ bottomActions={[]}
163
+ style={{
164
+ paddingBlock: 12,
165
+ width: 48,
166
+ }}
167
+ topActions={items.map((item) => (
168
+ <ActionIcon
169
+ active={tab === item.key}
170
+ key={item.key}
171
+ onClick={() => setTab(item.key)}
172
+ placement={'right'}
173
+ title={item.key}
174
+ >
175
+ {item.icon}
176
+ </ActionIcon>
177
+ ))}
178
+ />
179
+ <Flexbox
180
+ height={'100%'}
181
+ style={{ overflow: 'hidden', position: 'relative' }}
182
+ width={'100%'}
183
+ >
184
+ <Flexbox
185
+ align={'center'}
186
+ className={`panel-drag-handle ${styles.header}`}
187
+ horizontal
188
+ justify={'space-between'}
189
+ >
190
+ <Flexbox align={'baseline'} gap={6} horizontal>
191
+ <b>{BRANDING_NAME} Dev Tools</b>
192
+ <span style={{ color: theme.colorTextDescription }}>/</span>
193
+ <span style={{ color: theme.colorTextDescription }}>{tab}</span>
194
+ </Flexbox>
195
+ <ActionIcon icon={XIcon} onClick={() => setIsExpanded(false)} />
196
+ </Flexbox>
197
+ {items.map((item) => (
198
+ <Flexbox
199
+ flex={1}
200
+ height={'100%'}
201
+ key={item.key}
202
+ style={{
203
+ display: tab === item.key ? 'flex' : 'none',
204
+ overflow: 'hidden',
205
+ }}
206
+ >
207
+ {item.children}
208
+ </Flexbox>
209
+ ))}
210
+ </Flexbox>
211
+ </Flexbox>
212
+ </Rnd>
213
+ )}
214
+ </>
215
+ );
216
+ });
217
+
218
+ export default CollapsibleFloatPanel;
@@ -0,0 +1,50 @@
1
+ import { ActionIcon, type ActionIconProps } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import React, { ReactNode } from 'react';
4
+ import { Flexbox, FlexboxProps } from 'react-layout-kit';
5
+
6
+ const useStyles = createStyles(({ token, css }) => ({
7
+ header: css`
8
+ border-block-end: 1px solid ${token.colorBorderSecondary};
9
+ `,
10
+ title: css`
11
+ font-weight: 550;
12
+ `,
13
+ }));
14
+
15
+ interface HeaderProps extends Omit<FlexboxProps, 'title' | 'children'> {
16
+ actions?: ActionIconProps[];
17
+ extra?: ReactNode;
18
+ title?: ReactNode;
19
+ }
20
+
21
+ const Header = ({ title, actions = [], extra, ...rest }: HeaderProps) => {
22
+ const { styles } = useStyles();
23
+
24
+ return (
25
+ <Flexbox
26
+ align={'center'}
27
+ className={styles.header}
28
+ flex={'none'}
29
+ height={46}
30
+ horizontal
31
+ justify={'space-between'}
32
+ paddingInline={16}
33
+ {...rest}
34
+ >
35
+ <div className={styles.title}>{title}</div>
36
+ <Flexbox align={'center'} gap={4} horizontal>
37
+ {extra}
38
+ {actions.map((action, index) => (
39
+ <ActionIcon
40
+ {...action}
41
+ key={action.title || index}
42
+ size={{ blockSize: 28, fontSize: 16 }}
43
+ />
44
+ ))}
45
+ </Flexbox>
46
+ </Flexbox>
47
+ );
48
+ };
49
+
50
+ export default Header;
@@ -0,0 +1,73 @@
1
+ import { Typography } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import dayjs from 'dayjs';
4
+ import { get, isDate } from 'lodash-es';
5
+ import React, { useMemo } from 'react';
6
+
7
+ import TooltipContent from './TooltipContent';
8
+
9
+ const { Text } = Typography;
10
+
11
+ const useStyles = createStyles(({ token, css }) => ({
12
+ cell: css`
13
+ font-family: ${token.fontFamilyCode};
14
+ font-size: ${token.fontSizeSM}px;
15
+ `,
16
+ tooltip: css`
17
+ border: 1px solid ${token.colorBorder};
18
+
19
+ font-family: ${token.fontFamilyCode};
20
+ font-size: ${token.fontSizeSM}px;
21
+ color: ${token.colorText} !important;
22
+ word-break: break-all;
23
+
24
+ background: ${token.colorBgElevated} !important;
25
+ `,
26
+ }));
27
+
28
+ interface TableCellProps {
29
+ column: string;
30
+ dataItem: any;
31
+ rowIndex: number;
32
+ }
33
+
34
+ const TableCell = ({ dataItem, column, rowIndex }: TableCellProps) => {
35
+ const { styles } = useStyles();
36
+ const data = get(dataItem, column);
37
+ const content = useMemo(() => {
38
+ if (isDate(data)) return dayjs(data).format('YYYY-MM-DD HH:mm:ss');
39
+
40
+ switch (typeof data) {
41
+ case 'object': {
42
+ return JSON.stringify(data);
43
+ }
44
+
45
+ case 'boolean': {
46
+ return data ? 'True' : 'False';
47
+ }
48
+
49
+ default: {
50
+ return data;
51
+ }
52
+ }
53
+ }, [data]);
54
+
55
+ return (
56
+ <td key={column} onDoubleClick={() => console.log('Edit cell:', rowIndex, column)}>
57
+ <Text
58
+ className={styles.cell}
59
+ ellipsis={{
60
+ tooltip: {
61
+ arrow: false,
62
+ classNames: { body: styles.tooltip },
63
+ title: <TooltipContent>{content}</TooltipContent>,
64
+ },
65
+ }}
66
+ >
67
+ {content}
68
+ </Text>
69
+ </td>
70
+ );
71
+ };
72
+
73
+ export default TableCell;
@@ -0,0 +1,39 @@
1
+ import { Highlighter } from '@lobehub/ui';
2
+ import Link from 'next/link';
3
+ import { ReactNode, memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ const TooltipContent = memo<{ children: ReactNode }>(({ children }) => {
7
+ if (typeof children !== 'string') return children;
8
+
9
+ if (children.startsWith('data:image')) {
10
+ return <img src={children} style={{ height: 'auto', maxWidth: '100%' }} />;
11
+ }
12
+
13
+ if (children.startsWith('http'))
14
+ return (
15
+ <Link href={children} target={'_blank'}>
16
+ {children}
17
+ </Link>
18
+ );
19
+
20
+ const code = children.trim().trimEnd();
21
+
22
+ if ((code.startsWith('{') && code.endsWith('}')) || (code.startsWith('[') && code.endsWith(']')))
23
+ return (
24
+ <Highlighter
25
+ language={'json'}
26
+ style={{
27
+ maxHeight: 400,
28
+ overflow: 'auto',
29
+ }}
30
+ type={'pure'}
31
+ >
32
+ {JSON.stringify(JSON.parse(code), null, 2)}
33
+ </Highlighter>
34
+ );
35
+
36
+ return <Flexbox>{children}</Flexbox>;
37
+ });
38
+
39
+ export default TooltipContent;