import { sendGetRequest, sendSpacePostRequest } from '@/utils/request'; import Icon from '@ant-design/icons'; import { OnChange } from '@monaco-editor/react'; import { useRequest } from 'ahooks'; import { Button, Input, Select, Table, Tooltip, Tree } from 'antd'; import type { DataNode } from 'antd/es/tree'; import { useSearchParams } from 'next/navigation'; import { ChangeEvent, Key, useEffect, useMemo, useState } from 'react'; import Chart from '../chart'; import Header from './header'; import MonacoEditor, { ISession } from './monaco-editor'; import SplitScreenHeight from '@/components/icons/split-screen-height'; import SplitScreenWeight from '@/components/icons/split-screen-width'; import { CaretRightOutlined, LeftOutlined, RightOutlined, SaveFilled } from '@ant-design/icons'; import { ColumnType } from 'antd/es/table'; import classNames from 'classnames'; import MyEmpty from '../common/MyEmpty'; import Database from '../icons/database'; import Field from '../icons/field'; import TableIcon from '../icons/table'; const { Search } = Input; type ITableData = { columns: string[]; values: (string | number)[][]; }; interface EditorValueProps { sql?: string; thoughts?: string; title?: string; showcase?: string; } interface RoundProps { db_name: string; round: number; round_name: string; } interface IProps { editorValue?: EditorValueProps; chartData?: any; tableData?: ITableData; layout?: 'TB' | 'LR'; tables?: any; handleChange: OnChange; } interface ITableTreeItem { title: string; key: string; type: string; default_value: string | null; can_null: string; comment: string | null; children: Array; } function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, tables, handleChange }: IProps) { const chartWrapper = useMemo(() => { if (!chartData) return null; return (
); }, [chartData]); const { columns, dataSource } = useMemo<{ columns: ColumnType[]; dataSource: Record[]; }>(() => { const { columns: cols = [], values: vals = [] } = tableData ?? {}; const tbCols = cols.map>(item => ({ key: item, dataIndex: item, title: item, })); const tbDatas = vals.map(row => { return row.reduce>((acc, item, index) => { acc[cols[index]] = item; return acc; }, {}); }); return { columns: tbCols, dataSource: tbDatas, }; }, [tableData]); const session: ISession = useMemo(() => { const map: Record = {}; const db = tables?.data; const tableList = db?.children; tableList?.forEach((table: ITableTreeItem) => { map[table.title] = table.children.map((column: ITableTreeItem) => { return { columnName: column.title, columnType: column.type, }; }); }); return { async getTableList(schemaName: any) { if (schemaName && schemaName !== db?.title) { return []; } return tableList?.map((table: ITableTreeItem) => table.title) || []; }, async getTableColumns(tableName: any) { return map[tableName] || []; }, async getSchemaList() { return db?.title ? [db?.title] : []; }, }; }, [tables]); return (
{tableData?.values.length ? ( ) : (
)} {chartWrapper} ); } function DbEditor() { const [expandedKeys, setExpandedKeys] = useState([]); const [searchValue, setSearchValue] = useState(''); const [currentRound, setCurrentRound] = useState(); const [autoExpandParent, setAutoExpandParent] = useState(true); const [chartData, setChartData] = useState(); const [editorValue, setEditorValue] = useState(); const [newEditorValue, setNewEditorValue] = useState(); const [tableData, setTableData] = useState<{ columns: string[]; values: (string | number)[] }>(); const [currentTabIndex, setCurrentTabIndex] = useState(); const [isMenuExpand, setIsMenuExpand] = useState(false); const [layout, setLayout] = useState<'TB' | 'LR'>('TB'); const searchParams = useSearchParams(); const id = searchParams?.get('id'); const scene = searchParams?.get('scene'); const { data: rounds } = useRequest( async () => await sendGetRequest('/v1/editor/sql/rounds', { con_uid: id, }), { onSuccess: res => { const lastItem = res?.data?.[res?.data?.length - 1]; if (lastItem) { setCurrentRound(lastItem?.round); } }, }, ); const { run: runSql, loading: runLoading } = useRequest( async () => { const db_name = rounds?.data?.find((item: any) => item.round === currentRound)?.db_name; return await sendSpacePostRequest(`/api/v1/editor/sql/run`, { db_name, sql: newEditorValue?.sql, }); }, { manual: true, onSuccess: res => { setTableData({ columns: res?.data?.colunms, values: res?.data?.values, }); }, }, ); const { run: runCharts, loading: runChartsLoading } = useRequest( async () => { const db_name = rounds?.data?.find((item: any) => item.round === currentRound)?.db_name; const params: { db_name: string; sql?: string; chart_type?: string; } = { db_name, sql: newEditorValue?.sql, }; if (scene === 'chat_dashboard') { params['chart_type'] = newEditorValue?.showcase; } return await sendSpacePostRequest(`/api/v1/editor/chart/run`, params); }, { manual: true, ready: !!newEditorValue?.sql, onSuccess: res => { if (res?.success) { setTableData({ columns: res?.data?.sql_data?.colunms || [], values: res?.data?.sql_data?.values || [], }); if (!res?.data?.chart_values) { setChartData(undefined); } else { setChartData({ type: res?.data?.chart_type, values: res?.data?.chart_values, title: newEditorValue?.title, description: newEditorValue?.thoughts, }); } } }, }, ); const { run: submitSql, loading: submitLoading } = useRequest( async () => { const db_name = rounds?.data?.find((item: RoundProps) => item.round === currentRound)?.db_name; return await sendSpacePostRequest(`/api/v1/sql/editor/submit`, { conv_uid: id, db_name, conv_round: currentRound, old_sql: editorValue?.sql, old_speak: editorValue?.thoughts, new_sql: newEditorValue?.sql, new_speak: newEditorValue?.thoughts?.match(/^\n--(.*)\n\n$/)?.[1]?.trim() || newEditorValue?.thoughts, }); }, { manual: true, onSuccess: res => { if (res?.success) { runSql(); } }, }, ); const { run: submitChart, loading: submitChartLoading } = useRequest( async () => { const db_name = rounds?.data?.find((item: any) => item.round === currentRound)?.db_name; return await sendSpacePostRequest(`/api/v1/chart/editor/submit`, { conv_uid: id, chart_title: newEditorValue?.title, db_name, old_sql: editorValue?.[currentTabIndex ?? 0]?.sql, new_chart_type: newEditorValue?.showcase, new_sql: newEditorValue?.sql, new_comment: newEditorValue?.thoughts?.match(/^\n--(.*)\n\n$/)?.[1]?.trim() || newEditorValue?.thoughts, gmt_create: new Date().getTime(), }); }, { manual: true, onSuccess: res => { if (res?.success) { runCharts(); } }, }, ); const { data: tables } = useRequest( async () => { const db_name = rounds?.data?.find((item: RoundProps) => item.round === currentRound)?.db_name; return await sendGetRequest('/v1/editor/db/tables', { db_name, page_index: 1, page_size: 200, }); }, { ready: !!rounds?.data?.find((item: RoundProps) => item.round === currentRound)?.db_name, refreshDeps: [rounds?.data?.find((item: RoundProps) => item.round === currentRound)?.db_name], }, ); const { run: handleGetEditorSql } = useRequest( async round => await sendGetRequest('/v1/editor/sql', { con_uid: id, round, }), { manual: true, onSuccess: res => { let sql = undefined; try { if (Array.isArray(res?.data)) { sql = res?.data; setCurrentTabIndex(0); } else if (typeof res?.data === 'string') { const d = JSON.parse(res?.data); sql = d; } else { sql = res?.data; } } catch (e) { console.log(e); } finally { setEditorValue(sql); if (Array.isArray(sql)) { setNewEditorValue(sql?.[Number(currentTabIndex || 0)]); } else { setNewEditorValue(sql); } } }, }, ); const treeData = useMemo(() => { const loop = (data: Array, parentKey?: string | number): DataNode[] => data.map((item: ITableTreeItem) => { const strTitle = item.title; const index = strTitle.indexOf(searchValue); const beforeStr = strTitle.substring(0, index); const afterStr = strTitle.slice(index + searchValue.length); const renderIcon = (type: string) => { switch (type) { case 'db': return ; case 'table': return ; default: return ; } }; const showTitle = index > -1 ? (
{renderIcon(item.type)}    {beforeStr} {searchValue} {afterStr}  {item?.type &&
{item?.type}
}
) : (
{renderIcon(item.type)}    {strTitle}  {item?.type &&
{item?.type}
}
); if (item.children) { const itemKey = parentKey ? String(parentKey) + '_' + item.key : item.key; return { title: strTitle, showTitle, key: itemKey, children: loop(item.children, itemKey) }; } return { title: strTitle, showTitle, key: item.key, }; }); if (tables?.data) { // default expand first node setExpandedKeys([tables?.data.key]); return loop([tables?.data]); } return []; }, [searchValue, tables]); const dataList = useMemo(() => { const res: { key: string | number; title: string; parentKey?: string | number }[] = []; const generateList = (data: DataNode[], parentKey?: string | number) => { if (!data || data?.length <= 0) return; for (let i = 0; i < data.length; i++) { const node = data[i]; const { key, title } = node; res.push({ key, title: title as string, parentKey }); if (node.children) { generateList(node.children, key); } } }; if (treeData) { generateList(treeData); } return res; }, [treeData]); const getParentKey = (key: Key, tree: DataNode[]): Key => { let parentKey: Key; for (let i = 0; i < tree.length; i++) { const node = tree[i]; if (node.children) { if (node.children.some(item => item.key === key)) { parentKey = node.key; } else if (getParentKey(key, node.children)) { parentKey = getParentKey(key, node.children); } } } return parentKey!; }; const onChange = (e: ChangeEvent) => { const { value } = e.target; if (tables?.data) { if (!value) { setExpandedKeys([]); } else { const newExpandedKeys = dataList .map(item => { if (item.title.indexOf(value) > -1) { return getParentKey(item.key, treeData); } return null; }) .filter((item, i, self) => item && self.indexOf(item) === i); setExpandedKeys(newExpandedKeys as Key[]); } setSearchValue(value); setAutoExpandParent(true); } }; useEffect(() => { if (currentRound) { handleGetEditorSql(currentRound); } }, [handleGetEditorSql, currentRound]); useEffect(() => { if (editorValue && scene === 'chat_dashboard' && currentTabIndex) { runCharts(); } }, [currentTabIndex, scene, editorValue, runCharts]); useEffect(() => { if (editorValue && scene !== 'chat_dashboard') { runSql(); } }, [scene, editorValue, runSql]); function resolveSqlAndThoughts(value: string | undefined) { if (!value) { return { sql: '', thoughts: '' }; } const match = value && value.match(/(--.*)?\n?([\s\S]*)/); let thoughts = ''; let sql; if (match && match.length >= 3) { thoughts = match[1]; sql = match[2]; } return { sql, thoughts }; } return (