@lobehub/chat 1.46.7 → 1.47.0

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 (50) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/package.json +2 -1
  4. package/src/app/(main)/discover/(detail)/provider/[slug]/features/ProviderConfig.tsx +2 -2
  5. package/src/app/(main)/settings/hooks/useCategory.tsx +3 -3
  6. package/src/app/(main)/settings/provider/(detail)/[id]/ClientMode.tsx +25 -0
  7. package/src/app/(main)/settings/provider/(detail)/[id]/page.tsx +2 -1
  8. package/src/app/(main)/settings/provider/ProviderMenu/SortProviderModal/index.tsx +0 -1
  9. package/src/database/client/migrations.json +11 -0
  10. package/src/database/repositories/tableViewer/index.test.ts +256 -0
  11. package/src/database/repositories/tableViewer/index.ts +251 -0
  12. package/src/database/server/models/aiProvider.ts +2 -2
  13. package/src/features/DevPanel/FloatPanel.tsx +136 -0
  14. package/src/features/DevPanel/PostgresViewer/DataTable/Table.tsx +157 -0
  15. package/src/features/DevPanel/PostgresViewer/DataTable/TableCell.tsx +34 -0
  16. package/src/features/DevPanel/PostgresViewer/DataTable/index.tsx +67 -0
  17. package/src/features/DevPanel/PostgresViewer/Schema.tsx +196 -0
  18. package/src/features/DevPanel/PostgresViewer/TableColumns.tsx +67 -0
  19. package/src/features/DevPanel/PostgresViewer/index.tsx +19 -0
  20. package/src/features/DevPanel/PostgresViewer/useTableColumns.ts +13 -0
  21. package/src/features/DevPanel/index.tsx +12 -0
  22. package/src/features/ModelSwitchPanel/index.tsx +4 -2
  23. package/src/hooks/useEnabledChatModels.ts +2 -2
  24. package/src/hooks/useModelContextWindowTokens.ts +2 -2
  25. package/src/hooks/useModelHasContextWindowToken.ts +2 -2
  26. package/src/hooks/useModelSupportToolUse.ts +2 -2
  27. package/src/hooks/useModelSupportVision.ts +2 -2
  28. package/src/layout/GlobalProvider/index.tsx +2 -2
  29. package/src/services/_auth.ts +2 -2
  30. package/src/services/aiModel/client.ts +60 -0
  31. package/src/services/aiModel/index.test.ts +10 -0
  32. package/src/services/aiModel/index.ts +5 -0
  33. package/src/services/aiModel/server.ts +47 -0
  34. package/src/services/aiModel/type.ts +30 -0
  35. package/src/services/aiProvider/client.ts +64 -0
  36. package/src/services/aiProvider/index.test.ts +10 -0
  37. package/src/services/aiProvider/index.ts +5 -0
  38. package/src/services/aiProvider/server.ts +43 -0
  39. package/src/services/aiProvider/type.ts +26 -0
  40. package/src/services/chat.ts +5 -5
  41. package/src/services/tableViewer/client.ts +16 -0
  42. package/src/services/tableViewer/index.ts +3 -0
  43. package/src/store/aiInfra/slices/aiProvider/action.ts +2 -2
  44. package/src/types/serverConfig.ts +6 -0
  45. package/src/types/tableViewer.ts +30 -0
  46. package/tests/utils.tsx +46 -0
  47. package/src/features/DebugUI/Content.tsx +0 -34
  48. package/src/features/DebugUI/index.tsx +0 -20
  49. package/src/services/aiModel.ts +0 -52
  50. package/src/services/aiProvider.ts +0 -47
@@ -165,7 +165,7 @@ export class AiProviderModel {
165
165
 
166
166
  getAiProviderById = async (
167
167
  id: string,
168
- decryptor: DecryptUserKeyVaults,
168
+ decryptor?: DecryptUserKeyVaults,
169
169
  ): Promise<AiProviderDetailItem | undefined> => {
170
170
  const query = this.db
171
171
  .select({
@@ -205,7 +205,7 @@ export class AiProviderModel {
205
205
  return { ...result, keyVaults } as AiProviderDetailItem;
206
206
  };
207
207
 
208
- getAiProviderRuntimeConfig = async (decryptor: DecryptUserKeyVaults) => {
208
+ getAiProviderRuntimeConfig = async (decryptor?: DecryptUserKeyVaults) => {
209
209
  const result = await this.db
210
210
  .select({
211
211
  fetchOnClient: aiProviders.fetchOnClient,
@@ -0,0 +1,136 @@
1
+ import { ActionIcon, Icon } from '@lobehub/ui';
2
+ import { FloatButton } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { BugIcon, BugOff, XIcon } from 'lucide-react';
5
+ import React, { PropsWithChildren, useEffect, useState } from 'react';
6
+ import { Flexbox } from 'react-layout-kit';
7
+ import { Rnd } from 'react-rnd';
8
+
9
+ // 定义样式
10
+ const useStyles = createStyles(({ token, css }) => {
11
+ return {
12
+ collapsed: css`
13
+ pointer-events: none;
14
+ transform: scale(0.8);
15
+ opacity: 0;
16
+ `,
17
+ content: css`
18
+ overflow: auto;
19
+ flex: 1;
20
+ height: 100%;
21
+ color: ${token.colorText};
22
+ `,
23
+
24
+ expanded: css`
25
+ pointer-events: auto;
26
+ transform: scale(1);
27
+ opacity: 1;
28
+ `,
29
+
30
+ header: css`
31
+ cursor: move;
32
+ user-select: none;
33
+
34
+ padding-block: 8px;
35
+ padding-inline: 16px;
36
+ border-block-end: 1px solid ${token.colorBorderSecondary};
37
+ border-start-start-radius: 12px;
38
+ border-start-end-radius: 12px;
39
+
40
+ font-weight: ${token.fontWeightStrong};
41
+ color: ${token.colorText};
42
+
43
+ background: ${token.colorFillAlter};
44
+ `,
45
+ panel: css`
46
+ position: fixed;
47
+ z-index: 1000;
48
+
49
+ overflow: hidden;
50
+ display: flex;
51
+
52
+ border-radius: 12px;
53
+
54
+ background: ${token.colorBgContainer};
55
+ box-shadow: ${token.boxShadow};
56
+
57
+ transition: opacity ${token.motionDurationMid} ${token.motionEaseInOut};
58
+ `,
59
+ };
60
+ });
61
+
62
+ const minWidth = 800;
63
+ const minHeight = 600;
64
+
65
+ const CollapsibleFloatPanel = ({ children }: PropsWithChildren) => {
66
+ const { styles } = useStyles();
67
+ const [isExpanded, setIsExpanded] = useState(false);
68
+ const [position, setPosition] = useState({ x: 100, y: 100 });
69
+ const [size, setSize] = useState({ height: minHeight, width: minWidth });
70
+
71
+ useEffect(() => {
72
+ try {
73
+ const localStoragePosition = localStorage.getItem('debug-panel-position');
74
+ if (localStoragePosition && JSON.parse(localStoragePosition)) {
75
+ setPosition(JSON.parse(localStoragePosition));
76
+ }
77
+ } catch {
78
+ /* empty */
79
+ }
80
+
81
+ try {
82
+ const localStorageSize = localStorage.getItem('debug-panel-size');
83
+ if (localStorageSize && JSON.parse(localStorageSize)) {
84
+ setSize(JSON.parse(localStorageSize));
85
+ }
86
+ } catch {
87
+ /* empty */
88
+ }
89
+ }, []);
90
+
91
+ return (
92
+ <>
93
+ <FloatButton
94
+ icon={<Icon icon={isExpanded ? BugOff : BugIcon} />}
95
+ onClick={() => setIsExpanded(!isExpanded)}
96
+ style={{ bottom: 24, right: 24 }}
97
+ />
98
+ {isExpanded && (
99
+ <Rnd
100
+ bounds="window"
101
+ className={`${styles.panel} ${isExpanded ? styles.expanded : styles.collapsed}`}
102
+ dragHandleClassName="panel-drag-handle"
103
+ minHeight={minHeight}
104
+ minWidth={minWidth}
105
+ onDragStop={(e, d) => {
106
+ setPosition({ x: d.x, y: d.y });
107
+ }}
108
+ onResizeStop={(e, direction, ref, delta, position) => {
109
+ setSize({
110
+ height: Number(ref.style.height),
111
+ width: Number(ref.style.width),
112
+ });
113
+ setPosition(position);
114
+ }}
115
+ position={position}
116
+ size={size}
117
+ >
118
+ <Flexbox height={'100%'}>
119
+ <Flexbox
120
+ align={'center'}
121
+ className={`panel-drag-handle ${styles.header}`}
122
+ horizontal
123
+ justify={'space-between'}
124
+ >
125
+ 开发者面板
126
+ <ActionIcon icon={XIcon} onClick={() => setIsExpanded(false)} />
127
+ </Flexbox>
128
+ <Flexbox className={styles.content}>{children}</Flexbox>
129
+ </Flexbox>
130
+ </Rnd>
131
+ )}
132
+ </>
133
+ );
134
+ };
135
+
136
+ export default CollapsibleFloatPanel;
@@ -0,0 +1,157 @@
1
+ import { createStyles } from 'antd-style';
2
+ import React from 'react';
3
+ import { Center } from 'react-layout-kit';
4
+ import { TableVirtuoso } from 'react-virtuoso';
5
+ import useSWR from 'swr';
6
+
7
+ import { tableViewerService } from '@/services/tableViewer';
8
+ import { useGlobalStore } from '@/store/global';
9
+ import { systemStatusSelectors } from '@/store/global/selectors';
10
+
11
+ import { useTableColumns } from '../useTableColumns';
12
+ import TableCell from './TableCell';
13
+
14
+ const useStyles = createStyles(({ token, css }) => ({
15
+ columnList: css`
16
+ margin-inline-start: 32px;
17
+ font-size: ${token.fontSizeSM}px;
18
+ color: ${token.colorTextSecondary};
19
+
20
+ > div {
21
+ padding-block: ${token.paddingXS}px;
22
+ padding-inline: 0;
23
+ }
24
+ `,
25
+ table: css`
26
+ overflow: scroll hidden;
27
+ flex: 1;
28
+
29
+ table {
30
+ border-collapse: collapse;
31
+ width: 100%;
32
+ margin-inline-end: 12px;
33
+ font-family: ${token.fontFamilyCode};
34
+ }
35
+
36
+ thead {
37
+ tr {
38
+ outline: 1px solid ${token.colorBorderSecondary};
39
+ }
40
+ }
41
+
42
+ th,
43
+ td {
44
+ overflow: hidden;
45
+
46
+ max-width: 200px;
47
+ padding-block: 8px;
48
+ padding-inline: 12px;
49
+ border-inline-end: 1px solid ${token.colorBorderSecondary};
50
+
51
+ font-size: 12px;
52
+ text-overflow: ellipsis;
53
+ white-space: nowrap;
54
+ }
55
+
56
+ th {
57
+ position: sticky;
58
+ z-index: 1;
59
+ inset-block-start: 0;
60
+
61
+ border-block-end: 1px solid ${token.colorBorderSecondary};
62
+
63
+ font-weight: ${token.fontWeightStrong};
64
+ text-align: start;
65
+ text-wrap: nowrap;
66
+
67
+ background: ${token.colorBgElevated};
68
+ }
69
+
70
+ td {
71
+ border-block-end: 1px solid ${token.colorBorderSecondary};
72
+ text-wrap: nowrap;
73
+ }
74
+
75
+ tbody {
76
+ tr:hover {
77
+ background: ${token.colorFillTertiary};
78
+ }
79
+ }
80
+ `,
81
+ tableItem: css`
82
+ cursor: pointer;
83
+
84
+ display: flex;
85
+ gap: ${token.padding}px;
86
+ align-items: center;
87
+
88
+ padding: 12px;
89
+ border-radius: ${token.borderRadius}px;
90
+
91
+ color: ${token.colorText};
92
+ `,
93
+ }));
94
+
95
+ interface TableProps {
96
+ tableName?: string;
97
+ }
98
+
99
+ const Table = ({ tableName }: TableProps) => {
100
+ const { styles } = useStyles();
101
+
102
+ const tableColumns = useTableColumns(tableName);
103
+ const isDBInited = useGlobalStore(systemStatusSelectors.isDBInited);
104
+
105
+ const tableData = useSWR(
106
+ isDBInited && tableName ? ['fetch-table-data', tableName] : null,
107
+ ([, table]) => tableViewerService.getTableData(table),
108
+ );
109
+
110
+ const columns = tableColumns.data?.map((t) => t.name) || [];
111
+ const isLoading = tableColumns.isLoading || tableData.isLoading;
112
+
113
+ if (!tableName) return <Center height={'80%'}>Select a table to view data</Center>;
114
+
115
+ if (isLoading) return <Center height={'80%'}>Loading...</Center>;
116
+
117
+ const dataSource = tableData.data?.data || [];
118
+ const header = (
119
+ <tr>
120
+ {columns.map((column) => (
121
+ <th key={column}>{column}</th>
122
+ ))}
123
+ </tr>
124
+ );
125
+
126
+ return (
127
+ <div className={styles.table}>
128
+ {dataSource.length === 0 ? (
129
+ <>
130
+ <table>
131
+ <thead>{header}</thead>
132
+ </table>
133
+ <Center height={400}>no rows</Center>
134
+ </>
135
+ ) : (
136
+ <TableVirtuoso
137
+ data={dataSource}
138
+ fixedHeaderContent={() => header}
139
+ itemContent={(index, row) => (
140
+ <>
141
+ {columns.map((column) => (
142
+ <TableCell
143
+ column={column}
144
+ dataItem={row}
145
+ key={`${column}_${index}`}
146
+ rowIndex={index}
147
+ />
148
+ ))}
149
+ </>
150
+ )}
151
+ />
152
+ )}
153
+ </div>
154
+ );
155
+ };
156
+
157
+ export default Table;
@@ -0,0 +1,34 @@
1
+ import React, { useMemo } from 'react';
2
+
3
+ interface TableCellProps {
4
+ column: string;
5
+ dataItem: any;
6
+ rowIndex: number;
7
+ }
8
+
9
+ const TableCell = ({ dataItem, column, rowIndex }: TableCellProps) => {
10
+ const data = dataItem[column];
11
+ const content = useMemo(() => {
12
+ switch (typeof data) {
13
+ case 'object': {
14
+ return JSON.stringify(data);
15
+ }
16
+
17
+ case 'boolean': {
18
+ return data ? 'True' : 'False';
19
+ }
20
+
21
+ default: {
22
+ return data;
23
+ }
24
+ }
25
+ }, [data]);
26
+
27
+ return (
28
+ <td key={column} onDoubleClick={() => console.log('Edit cell:', rowIndex, column)}>
29
+ {content}
30
+ </td>
31
+ );
32
+ };
33
+
34
+ export default TableCell;
@@ -0,0 +1,67 @@
1
+ import { ActionIcon, Icon } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { Download, Filter, RefreshCw } from 'lucide-react';
5
+ import React from 'react';
6
+
7
+ import Table from './Table';
8
+
9
+ const useStyles = createStyles(({ token, css }) => ({
10
+ dataPanel: css`
11
+ overflow: hidden;
12
+ display: flex;
13
+ flex: 1;
14
+ flex-direction: column;
15
+
16
+ height: 100%;
17
+
18
+ background: ${token.colorBgContainer};
19
+ `,
20
+ toolbar: css`
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: space-between;
24
+
25
+ padding-block: 12px;
26
+ padding-inline: 16px;
27
+ border-block-end: 1px solid ${token.colorBorderSecondary};
28
+ `,
29
+ toolbarButtons: css`
30
+ display: flex;
31
+ gap: 4px;
32
+ `,
33
+ toolbarTitle: css`
34
+ font-size: ${token.fontSizeLG}px;
35
+ font-weight: ${token.fontWeightStrong};
36
+ color: ${token.colorText};
37
+ `,
38
+ }));
39
+
40
+ interface DataTableProps {
41
+ tableName: string;
42
+ }
43
+
44
+ const DataTable = ({ tableName }: DataTableProps) => {
45
+ const { styles } = useStyles();
46
+
47
+ return (
48
+ <div className={styles.dataPanel}>
49
+ {/* Toolbar */}
50
+ <div className={styles.toolbar}>
51
+ <div className={styles.toolbarTitle}>{tableName || 'Select a table'}</div>
52
+ <div className={styles.toolbarButtons}>
53
+ <Button color={'default'} icon={<Icon icon={Filter} />} variant={'filled'}>
54
+ Filter
55
+ </Button>
56
+ <ActionIcon icon={Download} title={'Export'} />
57
+ <ActionIcon icon={RefreshCw} title={'Refresh'} />
58
+ </div>
59
+ </div>
60
+
61
+ {/* Table */}
62
+ <Table tableName={tableName} />
63
+ </div>
64
+ );
65
+ };
66
+
67
+ export default DataTable;
@@ -0,0 +1,196 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { ChevronDown, ChevronRight, Database, Table as TableIcon } from 'lucide-react';
4
+ import React, { useState } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+ import useSWR from 'swr';
7
+
8
+ import { tableViewerService } from '@/services/tableViewer';
9
+ import { useGlobalStore } from '@/store/global';
10
+ import { systemStatusSelectors } from '@/store/global/selectors';
11
+
12
+ import TableColumns from './TableColumns';
13
+
14
+ // 样式定义
15
+ const useStyles = createStyles(({ token, css }) => ({
16
+ button: css`
17
+ cursor: pointer;
18
+
19
+ display: flex;
20
+ gap: 4px;
21
+ align-items: center;
22
+
23
+ padding-block: ${token.paddingXS}px;
24
+ padding-inline: ${token.padding}px;
25
+ border: none;
26
+ border-radius: ${token.borderRadius}px;
27
+
28
+ color: ${token.colorText};
29
+
30
+ background: ${token.colorFillSecondary};
31
+
32
+ transition: all ${token.motionDurationMid};
33
+
34
+ &:hover {
35
+ background: ${token.colorFillTertiary};
36
+ }
37
+ `,
38
+ count: css`
39
+ font-size: 12px;
40
+ color: ${token.colorTextTertiary};
41
+ `,
42
+ dataPanel: css`
43
+ overflow: hidden;
44
+ display: flex;
45
+ flex: 1;
46
+ flex-direction: column;
47
+
48
+ height: 100%;
49
+
50
+ background: ${token.colorBgContainer};
51
+ `,
52
+ schema: css`
53
+ font-family: ${token.fontFamilyCode};
54
+ font-size: 12px;
55
+ font-weight: normal;
56
+ color: ${token.colorTextTertiary};
57
+ `,
58
+ schemaHeader: css`
59
+ display: flex;
60
+ gap: 8px;
61
+ align-items: center;
62
+
63
+ padding-block: 12px;
64
+ padding-inline: 16px;
65
+
66
+ font-weight: ${token.fontWeightStrong};
67
+ color: ${token.colorText};
68
+ `,
69
+ schemaPanel: css`
70
+ overflow: scroll;
71
+
72
+ width: 280px;
73
+ height: 100%;
74
+ border-inline-end: 1px solid ${token.colorBorderSecondary};
75
+
76
+ background: ${token.colorBgContainer};
77
+ `,
78
+ selected: css`
79
+ background: ${token.colorFillSecondary};
80
+ `,
81
+ table: css`
82
+ overflow: hidden;
83
+ flex: 1;
84
+
85
+ table {
86
+ border-collapse: collapse;
87
+ width: 100%;
88
+ }
89
+
90
+ th {
91
+ position: sticky;
92
+ z-index: 1;
93
+ inset-block-start: 0;
94
+
95
+ padding: ${token.padding}px;
96
+ border-block-end: 1px solid ${token.colorBorderSecondary};
97
+
98
+ font-weight: ${token.fontWeightStrong};
99
+ text-align: start;
100
+
101
+ background: ${token.colorFillQuaternary};
102
+ }
103
+
104
+ td {
105
+ padding: ${token.padding}px;
106
+ border-block-end: 1px solid ${token.colorBorderSecondary};
107
+ transition: all ${token.motionDurationMid};
108
+
109
+ &:hover {
110
+ background: ${token.colorFillQuaternary};
111
+ }
112
+ }
113
+ `,
114
+ tableItem: css`
115
+ cursor: pointer;
116
+
117
+ display: flex;
118
+ gap: 8px;
119
+ align-items: center;
120
+
121
+ margin-inline: 8px;
122
+ padding: 8px;
123
+ border-radius: ${token.borderRadius}px;
124
+
125
+ color: ${token.colorText};
126
+
127
+ &:hover {
128
+ background: ${token.colorFillSecondary};
129
+ }
130
+ `,
131
+ }));
132
+
133
+ interface SchemaPanelProps {
134
+ onTableSelect: (tableName: string) => void;
135
+ selectedTable?: string;
136
+ }
137
+ const SchemaPanel = ({ onTableSelect, selectedTable }: SchemaPanelProps) => {
138
+ const { styles, cx } = useStyles();
139
+ const [expandedTables, setExpandedTables] = useState(new Set());
140
+
141
+ const isDBInited = useGlobalStore(systemStatusSelectors.isDBInited);
142
+
143
+ const { data, isLoading } = useSWR(isDBInited ? 'fetch-tables' : null, () =>
144
+ tableViewerService.getAllTables(),
145
+ );
146
+
147
+ const toggleTable = (tableName: string) => {
148
+ const newExpanded = new Set(expandedTables);
149
+ if (newExpanded.has(tableName)) {
150
+ newExpanded.delete(tableName);
151
+ } else {
152
+ newExpanded.add(tableName);
153
+ }
154
+ setExpandedTables(newExpanded);
155
+ };
156
+
157
+ return (
158
+ <div className={styles.schemaPanel}>
159
+ <div className={styles.schemaHeader}>
160
+ <Database size={16} />
161
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
162
+ <span>Tables {data?.length}</span>
163
+ <span className={styles.schema}>public</span>
164
+ </Flexbox>
165
+ </div>
166
+ {isLoading ? (
167
+ <div>Loading...</div>
168
+ ) : (
169
+ <Flexbox>
170
+ {data?.map((table) => (
171
+ <div key={table.name}>
172
+ <Flexbox
173
+ className={cx(styles.tableItem, selectedTable === table.name && styles.selected)}
174
+ horizontal
175
+ onClick={() => {
176
+ toggleTable(table.name);
177
+ onTableSelect(table.name);
178
+ }}
179
+ >
180
+ <Icon icon={expandedTables.has(table.name) ? ChevronDown : ChevronRight} />
181
+ <TableIcon size={16} />
182
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
183
+ <span>{table.name}</span>
184
+ <span className={styles.count}>{table.count}</span>
185
+ </Flexbox>
186
+ </Flexbox>
187
+ {expandedTables.has(table.name) && <TableColumns tableName={table.name} />}
188
+ </div>
189
+ ))}
190
+ </Flexbox>
191
+ )}
192
+ </div>
193
+ );
194
+ };
195
+
196
+ export default SchemaPanel;
@@ -0,0 +1,67 @@
1
+ import { Tag } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+ import React from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useTableColumns } from './useTableColumns';
7
+
8
+ const useStyles = createStyles(({ token, css }) => ({
9
+ container: css`
10
+ margin-inline: 40px 4px;
11
+ font-size: ${token.fontSizeSM}px;
12
+ color: ${token.colorTextSecondary};
13
+ `,
14
+ item: css`
15
+ padding-block: 4px;
16
+ padding-inline: 0;
17
+ font-family: ${token.fontFamilyCode};
18
+ `,
19
+ type: css`
20
+ font-size: 10px;
21
+ color: ${token.red9};
22
+ `,
23
+ }));
24
+
25
+ interface TableColumnsProps {
26
+ tableName: string;
27
+ }
28
+
29
+ const TableColumns = ({ tableName }: TableColumnsProps) => {
30
+ const { styles } = useStyles();
31
+
32
+ const { data, isLoading } = useTableColumns(tableName);
33
+
34
+ return (
35
+ <div className={styles.container}>
36
+ {isLoading ? (
37
+ <div>Loading...</div>
38
+ ) : (
39
+ <Flexbox>
40
+ {data?.map((column) => (
41
+ <Flexbox
42
+ align={'center'}
43
+ className={styles.item}
44
+ horizontal
45
+ justify={'space-between'}
46
+ key={column.name}
47
+ >
48
+ <Flexbox>
49
+ <Flexbox>{column.name}</Flexbox>
50
+ <span className={styles.type}>{column.type}</span>
51
+ </Flexbox>
52
+ {column.isPrimaryKey && (
53
+ <div>
54
+ <Tag bordered={false} color={'cyan'}>
55
+ Primary
56
+ </Tag>
57
+ </div>
58
+ )}
59
+ </Flexbox>
60
+ ))}
61
+ </Flexbox>
62
+ )}
63
+ </div>
64
+ );
65
+ };
66
+
67
+ export default TableColumns;
@@ -0,0 +1,19 @@
1
+ import React, { useState } from 'react';
2
+ import { Flexbox } from 'react-layout-kit';
3
+
4
+ import DataTable from './DataTable';
5
+ import SchemaPanel from './Schema';
6
+
7
+ // Main Database Panel Component
8
+ const DatabasePanel = () => {
9
+ const [selectedTable, setSelectedTable] = useState<string>('');
10
+
11
+ return (
12
+ <Flexbox height={'100%'} horizontal>
13
+ <SchemaPanel onTableSelect={setSelectedTable} selectedTable={selectedTable} />
14
+ <DataTable tableName={selectedTable} />
15
+ </Flexbox>
16
+ );
17
+ };
18
+
19
+ export default DatabasePanel;
@@ -0,0 +1,13 @@
1
+ import useSWR from 'swr';
2
+
3
+ import { tableViewerService } from '@/services/tableViewer';
4
+ import { useGlobalStore } from '@/store/global';
5
+ import { systemStatusSelectors } from '@/store/global/selectors';
6
+
7
+ export const useTableColumns = (tableName?: string) => {
8
+ const isDBInited = useGlobalStore(systemStatusSelectors.isDBInited);
9
+
10
+ return useSWR(isDBInited && tableName ? ['fetch-table-columns', tableName] : null, ([, table]) =>
11
+ tableViewerService.getTableDetails(table),
12
+ );
13
+ };