Commit 7ec6916c authored by 熊洋洋's avatar 熊洋洋

feat: init project 🚀🔥🔥

parents
{
"extends": "next/core-web-vitals",
"rules": {
"react-hooks/rules-of-hooks": "off", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "off",// Checks effect dependencies
}
}
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
backend
TOKEN.ts
.env
.vscode
\ No newline at end of file
**/*.svg
package.json
out
.DS_Store
*.png
.editorconfig
Dockerfile*
.gitignore
.prettierignore
LICENSE
.next
# .prettierrc
tabWidth: 4
semi: true
singleQuote: true
trailingComma: 'es5'
bracketSpacing: true
jsxBracketSameLine: false
arrowParens: 'avoid'
printWidth: 100
# CHAT-QUERY
[en](./README.md)
> Chat-Query 是一个基于元数据模型和 AI 技术,通过自然语言实现数据查询。
## 演示
> [view](https://chat-quey.netlify.app/)
> [demo](https://cdn.glitch.me/fd139a45-4a65-41b6-9634-41617ab20cdc/%E6%BC%94%E7%A4%BA.gif?v=1686907695067)
+ **功能特点🐂:**
- 支持导入 DDL、DBML 和数据库逆向解析,AI 自动生成业务模型。
- 提供业务模型的基本 CRUD 功能、AI 智能分析,支持模型导出为 DDL、DBML 以及与数据库同步。
- 结合模型和 AI 实现自然语言数据查询,可添加至查询列表并通过 API 调用。
## 应用场景🎬
+ 从低代码到零代码开发。
+ 非业务人员快速进行数据分析。
+ 更多应用场景待探索...
## 开发环境设置
> 👏 欢迎参与 Chat-Query 的建设。
+ 后端:
```js
pnpm start:dev
```
- 在 .env 文件中添加 OPEN_AI_API_KEY='sk-...'等环境变量
+ 前端
```js
pnpm install
gitpnpm dev
```
-在 .env 文件中添加 NEXT_PUBLIC_OPEN_AI_API_KEY='sk-...'
## 系统架构
![架构](https://cdn.glitch.global/fd139a45-4a65-41b6-9634-41617ab20cdc/%E6%97%A0%E6%A0%87%E9%A2%98-2023-05-31-1202%20(1).png?v=1686908252244)
\ No newline at end of file
# CHAT-QUERY
[中文](./README-zh.md)
> Chat-Query is a data query tool based on metadata models and AI technology, enabling natural language queries.
## Demo
> [view](https://chat-quey.netlify.app/)
> [demo](https://cdn.glitch.me/fd139a45-4a65-41b6-9634-41617ab20cdc/%E6%BC%94%E7%A4%BA.gif?v=1686907695067)
+ **Features🐂:**
- Supports importing DDL, DBML, and reverse database parsing, with AI automatically generating business models.
- Provides basic CRUD functionality for business models, AI intelligent analysis, and supports exporting models as DDL, DBML, and synchronizing with databases.
- Combines models and AI to enable natural language data queries, which can be added to the query list and called via API.
## Application Scenarios🎬
+ From low-code to no-code development.
+ Non-business users can quickly perform data analysis.
+ More application scenarios to be explored...
## Development Environment Setup
> 👏 Welcome to contribute to the development of Chat-Query.
+ Backend:
```js
pnpm start:dev
```
- Add environement variables such as OPEN_AI_API_KEY = 'sk-...' to the .env file
+ Frontend
```js
pnpm install
pnpm dev
```
- Add OPEN_AI_API_KEY='sk-...' in components/AITool/TOKEN.ts
## System Architecture
![Architecture](https://cdn.glitch.global/fd139a45-4a65-41b6-9634-41617ab20cdc/%E6%97%A0%E6%A0%87%E9%A2%98-2023-05-31-1202%20(1).png?v=1686908252244)
\ No newline at end of file
import getConfig from 'next/config';
import APi, { backendApi } from '.';
const {
publicRuntimeConfig: { apiPath },
} = getConfig();
export type DBInfo = {
host: string;
port: number;
user: string;
password: string;
database: string;
client: 'mysql2';
name?: string
};
export default class ConnectDb {
static create(config: DBInfo) {
return backendApi.post('query/testConnectDb', config);
}
static getDbDBML(config: DBInfo) {
return backendApi.post('query/getDbDBML', config);
}
static addDbForSchema(params: {
'config': DBInfo,
'schemaId':string,
'name':string
}) {
return backendApi.post('query/createDbConnect',params);
}
static getAllForSchema(schemaId:string) {
return backendApi.get(`query/${schemaId}/DbConnect`);
}
static removeDbForSchema(DbID:string) {
return backendApi.delete(`query/DbConnect/${DbID}`);
}
static addQuery(query: {
schemaId:string,
DbID:string,
name:string,
content: {
executions:{
content: string;
type: string
},
params:Record<string,any>
info: { queryDescription:string,queryName:string}
}
}) {
return backendApi.post("query/add",query)
}
static deleteQuery(queryId:string) {
return backendApi.delete(`/query/${queryId}`)
}
static getQueries(schemaId: string) {
return backendApi.get(`query/${schemaId}/queries`)
}
static runQuery(queryId: string,params:Record<string, any>) {
return backendApi.post(`query/run/${queryId}`,{params})
}
}
import getConfig from 'next/config';
import APi, { backendApi } from '.';
const {
publicRuntimeConfig: { apiPath },
} = getConfig();
export type ExecuteSqlPrams = Record<string, any>;
export default class ExecuteQuery {
static executeSql(config: ExecuteSqlPrams, execution: {
content: string;
type: string
}[], dbID: string,) {
return backendApi.post( '/query/querySql', {
config,
execution,
dbID,
});
}
}
import getConfig from 'next/config';
import type { NextApiRequest, NextApiResponse } from 'next';
import { table } from 'console';
import APi from '.';
const {
publicRuntimeConfig: { apiPath },
publicRuntimeConfig,
} = getConfig();
export interface schemaParams {
type: 'schema' | 'table';
name?: string;
}
export default class getSchema {
static getTableList(config: schemaParams) {
return APi.post(apiPath + 'getSchema', config);
}
}
import getConfig from 'next/config';
import APi, { backendApi } from '.';
const {
publicRuntimeConfig: { apiPath },
publicRuntimeConfig,
} = getConfig();
export interface View {
type: 'schema' | 'table';
name?: string;
}
export default class getView {
static getViewComponent(params: {
props: Record<string, any>,
need: string
}) {
return backendApi.post('/openAi/api/reactLive', params);
}
}
import axios from 'redaxios';
const APi = axios.create({
});
export const backendApi = axios.create({
"baseURL":process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:3001"
});
export default APi;
\ No newline at end of file
import React from 'react';
import MarkdownIt from 'markdown-it';
import mdHighlight from 'markdown-it-highlightjs';
// import 'katex/dist/katex.min.css';
import doMarkdownit from '@digitalocean/do-markdownit';
import Prism from '@digitalocean/do-markdownit/vendor/prismjs';
import prismTools from '@digitalocean/do-markdownit/vendor/prismjs/plugins/toolbar/prism-toolbar';
import prismCopyToClipboard from '@digitalocean/do-markdownit/vendor/prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard';
import { debounce } from 'lodash';
// import style manually
export interface ChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
interface Props {
role: ChatMessage['role'];
message: string;
}
// Finish!
const addPlugins = debounce(() => {
prismTools(Prism);
prismCopyToClipboard(Prism);
Prism.highlightAll();
}, 50);
export const htmlString = (message: string | (() => string)) => {
const md = MarkdownIt()
.use(mdHighlight)
.use(doMarkdownit, {
fence_environment: {
allowedEnvironments: '*',
},
fence_classes: {
allowedClasses: false,
},
callout: {
allowedClasses: ['note', 'warning', 'info', 'draft'],
},
});
addPlugins();
if (typeof message === 'function') {
return md.render(message());
} else if (typeof message === 'string') {
return md.render(message);
}
return '';
};
export function codeWrapper(type: string, code: string) {
return '``` ' + type + '\n' + code + '\n' + '```';
}
export default function MessageItem({ message, role }: Props) {
// role === 'user';
return (
<div
className={`flex gap-3 p-4 box-border mx-[5px] shadow rounded transition-colors mt-[20px] font-hm ${
role === 'user'
? 'bg-[rgb(var(--primary-6))] text-white shadow-[var(--pc)]'
: 'bg-[var(--white-bg)] text-[#333]'
}`}
>
<div
className="message prose text-slate break-words overflow-hidden"
dangerouslySetInnerHTML={{
__html: htmlString(message),
}}
/>
</div>
);
}
import {
Button,
Input,
Comment,
Avatar,
Skeleton,
Form,
Modal,
Spin,
Notification,
AutoComplete,
} from '@arco-design/web-react';
import { useTranslation } from 'react-i18next';
import MessageItem from './MessageItem';
import { debounce, filter, get, map } from 'lodash';
import React, {
ReactElement,
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react';
import { IconRecord, IconRobot, IconSend, IconSwap, IconVoice } from '@arco-design/web-react/icon';
import { getModel } from '@/utils/gpt';
// import useSpeechToText from 'react-hook-speech-to-text';
let useSpeechToText: any;
// (async () => {
// if (process.browser) useSpeechToText = (await import('react-hook-speech-to-text')).default;
// })();
export interface ChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
const getQuestion = (question: string) => `${question}`;
const model = getModel('gpt35');
type AIProps = {
context: any;
messageList: any[];
setMessageList: (value: any[]) => any;
startView: number;
renderMessageItem: any;
doneFx: (message: string) => any;
simpleMode?: string | true;
simpleModeVisible?: boolean;
setSimpleModeVisible?: () => any;
welcome: ReactElement | string;
quickTip?: string[] | { value: string; name: string; [key: string]: any }[];
searchFlag: RegExp;
inputProps?: Record<string, any>;
SendButton: ({ inputRef }: { inputRef: any }) => JSX.Element;
};
export function AIWrapper({
context,
messageList,
setMessageList,
startView = 0,
renderMessageItem,
doneFx,
simpleMode,
simpleModeVisible = false,
setSimpleModeVisible = () => {},
welcome = '',
quickTip = [],
searchFlag = /^\//,
inputProps = {},
SendButton,
}: AIProps) {
const input = useRef<HTMLInputElement>();
const panelRef = useRef<HTMLInputElement>();
const [currentAssistantMessage, setCurrentAssistantMessage] = useState('');
const [loading, setLoading] = useState(false);
const [_, refresh] = useReducer(state => state++, 0);
const scrollContainer = useRef();
const message = useRef([]);
const working = useRef(false);
const [Niche, setNiche] = useState('you');
const [tinking, setTinking] = useState(false);
const handleButtonClickSuccess = useEffect(() => {
if (!currentAssistantMessage || loading) {
return;
}
setMessageList([
...messageList,
{
role: 'assistant',
content: currentAssistantMessage,
},
]);
setCurrentAssistantMessage('');
// return clearContext;
}, [currentAssistantMessage, loading]);
const handleButtonClick = useCallback(
(message?: string, callBack?: (m: string) => void) => {
const inputRef = input.current?.dom;
const inputValue = message || inputRef.value;
if (!inputValue) {
return;
}
setLoading(true);
// @ts-ignore
inputRef.value = '';
setMessageList([
...messageList,
{
role: 'user',
content: inputValue,
},
]);
const toView = debounce(() => {
scrollContainer &&
scrollContainer?.current?.scrollIntoView({
behavior: 'smooth',
block: 'end',
});
}, 50);
toView();
fetch('/openai/v1/chat/completions', {
headers: {
'cache-control': 'no-cache',
'Content-Type': 'application/json',
Accept: 'text/event-stream',
},
body: JSON.stringify({
messages: [
...messageList,
{
role: 'user',
content: inputValue,
},
],
model: model.name,
temperature: model.temperature,
frequency_penalty: model.frequency_penalty,
presence_penalty: model.presence_penalty,
stream: true,
}),
method: 'POST',
}).then(async response => {
if (!response.ok) {
// throw new Error(response.statusText);
Notification.error({
title: 'No Response',
content: undefined,
});
// setLoading(false);
}
// response.text().then((text) => {
// setCurrentAssistantMessage(text);
// });
const data = response.body;
if (!data) {
Notification.error({
title: 'No data',
content: undefined,
});
// setLoading(false);
}
const reader = data.getReader();
const decoder = new TextDecoder('utf-8');
let done = false;
let currentAssistantMessageStr = currentAssistantMessage;
while (!done) {
const content = await reader.read();
const { done: readerDone, value } = content;
if (value) {
const char = decoder.decode(value);
if (char === '\n' && currentAssistantMessageStr.endsWith('\n')) {
continue;
}
const codeBlocks = char.trim().split(`
`);
for (let i = 0; i < codeBlocks.length - 1; i++) {
currentAssistantMessageStr += get(
JSON.parse(codeBlocks[i].replace(/^data:/g, '').trim()),
'choices[0].delta.content',
''
);
setCurrentAssistantMessage(currentAssistantMessageStr);
}
// if (char) {
// currentAssistantMessageStr += char;
// setCurrentAssistantMessage(currentAssistantMessageStr);
// }
}
done = readerDone;
}
setTinking(true);
setTimeout(() => {
setLoading(false);
setTinking(false);
}, 2000);
callBack && callBack(currentAssistantMessageStr);
doneFx && doneFx(currentAssistantMessageStr);
console.log(currentAssistantMessageStr, 'currentAssistantMessageStr');
setTimeout(toView, 100);
});
},
[
loading,
messageList,
currentAssistantMessage,
input,
handleButtonClickSuccess,
scrollContainer,
]
);
const clear = () => {
// clearContext().then(() => {
const inputRef = input.current.dom;
inputRef.value = '';
setMessageList(messageList.slice(0, startView));
setCurrentAssistantMessage('');
// });
};
const [visible, setVisible] = React.useState(false);
const user = {};
const userIcon = user?.photoId ? (
`/api/v1/assets/${user?.photoId}`
) : (
<Avatar
size={32}
className="shadow-lg m-[1px] bg-gradient-to-tr from-orange-300 to-[var(--pc)]"
>
{Niche}
</Avatar>
);
useEffect(() => {
context && context({ clear });
}, [context]);
const RenderMessageItem = renderMessageItem || MessageItem;
const { t } = useTranslation('actions');
const animaeString = t(
!quickTip.length
? 'Please input your question'
: 'Starting with "/", retrieve frequently asked questions'
);
const [placeholder, setPlaceholder] = useState('');
const [isSearching, setSearching] = useState<string | false>(false);
const quickTipData = useMemo(() => {
const el = input.current?.dom;
if (isSearching && el) {
console.log(
filter(quickTip, ({ value }: { value: string }) => {
if (el) {
return value.indexOf(isSearching.replace(searchFlag, '').trim()) !== -1;
}
})
);
return filter(quickTip, ({ value }: { value: string }) => {
if (el) {
return value.indexOf(isSearching.replace(searchFlag, '').trim()) !== -1;
}
});
}
return [];
}, [isSearching, quickTip, input]);
let timer = useRef<any>();
useEffect(() => {
timer.current && clearTimeout(timer.current);
if (simpleModeVisible) {
let f = 1;
let perStr = '';
const start = () => {
if (perStr.length === 0) {
f = 1;
} else if (perStr === animaeString) {
f = -1;
}
perStr = animaeString.slice(0, perStr.length + f);
setPlaceholder(perStr);
timer.current = setTimeout(() => {
start();
}, 300);
};
start();
}
return () => clearTimeout(timer.current);
}, [timer, simpleModeVisible, animaeString]);
const { error, interimResult, isRecording, results, startSpeechToText, stopSpeechToText } =
useSpeechToText({
continuous: true,
useLegacyResults: false,
speechRecognitionProperties: {
interimResults: true, // Allows for displaying real-time speech results
continuous: true,
},
});
const [beforeRecordValue, setBeforeRecordValue] = useState('');
const [recordValue, setRecordValue] = useState('');
useEffect(() => {
if (!isRecording) {
const el = input.current?.dom;
if (el) el.value = recordValue || '';
}
}, [isRecording, recordValue]);
useEffect(() => {
if (interimResult) {
const el = input.current?.dom;
if (el) el.value = beforeRecordValue + (interimResult || '');
}
}, [interimResult, beforeRecordValue]);
const inputValue = input.current?.dom.value || '';
const inputNode = (
<Input
ref={input}
placeholder={simpleModeVisible ? placeholder + '│' : animaeString}
allowClear={!isRecording}
style={{ width: 650 }}
height={56}
className="overflow-hidden shadow-md simple-mode shadow-cyan-500/30"
autoFocus
onPressEnter={() => handleButtonClick()}
value={inputValue}
onChange={value => {
const el = input.current?.dom;
if (el) {
el.value = value;
}
setRecordValue(value);
}}
onClear={() => {
const el = input.current?.dom;
if (el) {
el.value = '';
}
setRecordValue('');
setBeforeRecordValue('');
}}
suffix={
<>
<Button
shape="circle"
icon={isRecording ? <IconRecord className="animate-ping" /> : <IconVoice />}
type="text"
className="mx-[10px] text-[15px]"
size="mini"
onClick={() => {
if (isRecording) {
setRecordValue(input.current?.dom.value || '');
stopSpeechToText();
} else {
setBeforeRecordValue(input.current?.dom.value || '');
startSpeechToText();
}
}}
/>
{SendButton ? (
<SendButton inputRef={input} />
) : (
<Button
disabled={loading || isRecording}
shape="circle"
icon={<IconSend />}
size="mini"
type="primary"
onClick={() => handleButtonClick()}
/>
)}
</>
}
{...inputProps}
/>
);
if (simpleMode) {
if (simpleMode === 'input') {
return inputNode;
}
return simpleModeVisible ? (
<Modal
footer={null}
title={null}
visible={simpleModeVisible}
closeIcon={null}
maskClosable
// onOk={() => setVisible(false)}
onCancel={() => setSimpleModeVisible(false)}
maskStyle={{
background: 'rgb(94 171 165 / 0.1)',
backdropFilter: ' blur(4px) hue-rotate(90deg) opacity(0.5)',
// backdropFilter: ' hue-rotate(90deg)',
}}
wrapClassName="!flex justify-center items-center"
autoFocus={true}
modalRender={() => {
return !loading ? (
<div className="translate-y-[200px]">{inputNode}</div>
) : (
<Spin dot />
);
}}
/>
) : null;
}
return (
<div className="w-full p-[15px] rounded">
<div
className="overflow-y-auto overflow-x-hidden mr-[-10px] pr-[10px] pb-[10px]"
style={{
height: 'calc(100vh - 257px)',
}}
>
{
<Comment
align="right"
author={t('assistant')}
avatar={
<IconRobot
style={{ fontSize: '32px', color: 'rgb(var(--primary-6))' }}
/>
}
content={
welcome ? (
welcome
) : (
<MessageItem
message={t(
'Hello! I an CHAT QUERY ai, please describe your business!'
)}
role={'assistant'}
/>
)
}
key="message"
/>
}
{map(messageList.slice(startView), (message, index) => (
<Comment
align="right"
author={message.role === 'user' ? Niche : t('assistant')}
avatar={
message.role === 'user' ? (
userIcon
) : (
<IconRobot
style={{ fontSize: '32px', color: 'rgb(var(--primary-6))' }}
/>
)
}
content={
<RenderMessageItem
message={message.content}
role={message.role}
rawRender={MessageItem}
/>
}
key={index}
/>
))}
{loading && (
<Comment
align="right"
author={t('assistant')}
avatar={
<IconRobot
style={{ fontSize: '32px', color: 'rgb(var(--primary-6))' }}
/>
}
content={
!currentAssistantMessage ? (
<div className="my-[20px]">
<Skeleton
animation
text={{
rows: 5,
width: ['100%', '100%', '100%', '100%', 400],
}}
/>
</div>
) : (
// <div>
// {
// <RenderMessageItem
// message={currentAssistantMessage}
// role="assistant"
// />
// }
// </div>
<div className="my-[20px]">
<div
className={`flex gap-3 p-4 box-border shadow mx-[5px] rounded transition-colors mt-[20px] font-hm ${'bg-[var(--white-bg)] text-[#333]'}`}
>
<Form style={{ width: '100%' }} autoComplete="off">
<div className="py-[10px] pb-[30px] text-[20px] justify-between flex">
{t('A query has been generated for you')}
<span>
<IconSwap />
</span>
</div>
<pre
className="break-before-all"
style={{
whiteSpace: 'break-spaces',
}}
>
{currentAssistantMessage}
</pre>
</Form>
</div>
</div>
)
}
/>
)}
<div ref={scrollContainer} />
</div>
{loading ? (
<div className="animate-bounce">
<div className="h-12 my-4 flex items-center justify-center rounded-sm text-[30px] font-bold gradient-text">
{!tinking
? `${t('Analyzing requirements')}...`
: `${t('Creating query')}...`}
</div>
</div>
) : (
<Comment
className="items-stretch !mt-[5px] pt-[15px] mx-[-15px] px-[15px] shadow-t"
actions={[
<Button
key="0"
onClick={clear}
type="secondary"
className="shadow rounded"
disabled={loading || isRecording}
>
{t('reset')}
</Button>,
<Button
className="shadow rounded"
disabled={loading || isRecording}
key="1"
onClick={() => handleButtonClick()}
type="primary"
>
{t('send')}
</Button>,
<Button
key="2"
onClick={() => {
if (isRecording) {
setRecordValue(input.current?.dom.value || '');
stopSpeechToText();
} else {
setBeforeRecordValue(input.current?.dom.value || '');
startSpeechToText();
}
}}
type="secondary"
shape="circle"
>
{isRecording ? (
<IconRecord className="animate-ping text-[var(--pc)]" />
) : (
<IconVoice className="text-[var(--pc)]" />
)}
</Button>,
]}
align="right"
avatar={userIcon}
content={
<div>
<AutoComplete
data={quickTipData}
triggerElement={<Input.TextArea autoComplete="off" id="input" />}
autoFocus
disabled={loading}
placeholder={animaeString}
ref={input}
value={inputValue}
onChange={value => {
setRecordValue(value);
}}
onPressEnter={e => {
if (
e.key === 'Enter' &&
!e?.nativeEvent.isComposing &&
!isSearching
) {
console.log(e.ctrlKey);
if (e.ctrlKey) {
const el = input.current?.dom;
if (el) {
el.value = `${input.current?.dom?.value}\n`;
}
} else {
handleButtonClick();
}
}
}}
filterOption={false}
onSearch={value => {
if (searchFlag.test(value)) {
setSearching(value);
} else {
setSearching(false);
}
}}
triggerProps={{
position: 'top',
popupAlign: {
top: 16,
},
className: 'w-full',
// popupVisible: true,
}}
onSelect={(value: string) => {
const el = input.current?.dom;
if (el) {
el.value = value;
}
setSearching(false);
requestAnimationFrame(() => {
input.current?.focus();
});
}}
/>
</div>
}
/>
)}
</div>
);
}
export default function Ai(props: AIProps) {
const [_, refresh] = useReducer(state => state++, 0);
useEffect(() => {
(async () => {
if (window && !useSpeechToText) {
useSpeechToText = (await import('react-hook-speech-to-text')).default;
refresh();
}
})();
}, []);
return useSpeechToText ? <AIWrapper {...props} /> : null;
}
import { useCallback } from 'react';
import ReactFlow, {
MiniMap,
Controls,
Background,
useNodesState,
useEdgesState,
addEdge,
} from 'reactflow';
// 👇 you need to import the reactflow styles
import 'reactflow/dist/style.css';
const initialNodes = [];
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(params => setEdges(eds => addEdge(params, eds)), [setEdges]);
return (
<ReactFlow
className="canvas"
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
>
<MiniMap />
<Controls />
<Background />
</ReactFlow>
);
}
export default Flow;
import { Dropdown, Menu, Space, Divider } from '@arco-design/web-react';
import graphState from '../hooks/use-graph-state';
import tableModel from '../hooks/table-model';
import { useTranslation } from 'react-i18next';
export default function ContextMenu({ setShowModal, children }) {
const { version } = graphState.useContainer();
const { updateGraph, addTable } = tableModel();
const { t } = useTranslation('ContextMenu');
const menus = [
{
key: 'N',
title: t('Add New Table'),
action: () => addTable(),
},
{
key: 'I',
title: t('Import Table'),
action: () => setShowModal('import'),
},
{
key: 'line',
},
{
key: 'S',
title: t('Save Change'),
action: () => updateGraph(),
},
{
key: 'E',
title: t('Export Database'),
action: () => setShowModal('export'),
},
];
return (
<Dropdown
trigger="contextMenu"
position="bl"
droplist={
version !== 'currentVersion' ? null : (
<Menu className="context-menu">
{menus.map(item =>
item.key === 'line' ? (
<Divider key={item.key} className="context-menu-line" />
) : (
<Menu.Item
key={item.key}
className="context-menu-item"
onClick={item.action}
>
{item.title}
<Space size={4}>
<div className="arco-home-key">⌘</div>
<div className="arco-home-key">{item.key}</div>
</Space>
</Menu.Item>
)
)}
</Menu>
)
}
>
{children}
</Dropdown>
);
}
import { useState, useEffect } from 'react';
import { Modal, Notification, Tabs } from '@arco-design/web-react';
import Editor from '@monaco-editor/react';
import graphState from '../hooks/use-graph-state';
import exportSQL from '../utils/export-sql';
import { useTranslation } from 'react-i18next';
const TabPane = Tabs.TabPane;
/**
* It's a modal that displays the command to be exported
* @returns Modal component
*/
export default function ExportModal({ showModal, onCloseModal }) {
const [exportType, setExportType] = useState('dbml');
const [sqlValue, setSqlValue] = useState('');
const { t } = useTranslation('modal');
const { tableDict, linkDict, theme } = graphState.useContainer();
const copy = async () => {
try {
await window.navigator.clipboard.writeText(sqlValue);
Notification.success({
title: t('Copy Success'),
});
} catch (e) {
console.log(e);
Notification.error({
title: t('Copy Failed'),
});
}
};
useEffect(() => {
if (showModal === 'export') {
const sql = exportSQL(tableDict, linkDict, exportType);
setSqlValue(sql);
}
}, [showModal, exportType]);
const editor = (
<Editor
className={`!mt-0 ${theme === 'dark' ? 'bg-[#1e1e1e]' : ' bg-[#fff]'} mt-[10px]`}
language={exportType === 'dbml' ? 'apex' : 'sql'}
width="680px"
height="60vh"
theme={theme === 'dark' ? 'vs-dark' : 'light'}
value={sqlValue}
options={{
acceptSuggestionOnCommitCharacter: true,
acceptSuggestionOnEnter: 'on',
accessibilitySupport: 'auto',
autoIndent: false,
automaticLayout: true,
codeLens: true,
colorDecorators: true,
contextmenu: true,
cursorBlinking: 'blink',
cursorSmoothCaretAnimation: false,
cursorStyle: 'line',
disableLayerHinting: false,
disableMonospaceOptimizations: false,
dragAndDrop: false,
fixedOverflowWidgets: false,
folding: true,
foldingStrategy: 'auto',
fontLigatures: false,
formatOnPaste: false,
formatOnType: false,
hideCursorInOverviewRuler: false,
highlightActiveIndentGuide: true,
links: true,
mouseWheelZoom: false,
multiCursorMergeOverlapping: true,
multiCursorModifier: 'alt',
overviewRulerBorder: true,
overviewRulerLanes: 2,
quickSuggestions: true,
quickSuggestionsDelay: 100,
readOnly: false,
renderControlCharacters: false,
renderFinalNewline: true,
renderIndentGuides: true,
renderLineHighlight: 'line',
renderWhitespace: 'none',
revealHorizontalRightPadding: 300,
roundedSelection: true,
rulers: [],
scrollBeyondLastColumn: 5,
scrollBeyondLastLine: true,
selectOnLineNumbers: true,
selectionClipboard: true,
selectionHighlight: true,
showFoldingControls: 'mouseover',
smoothScrolling: false,
suggestOnTriggerCharacters: true,
wordBasedSuggestions: true,
wordSeparators: '~!@#$%^&*()-=+[{]}|;:\'",.<>/?',
wordWrap: 'wordWrapColumn',
wordWrapBreakAfterCharacters: '\t})]?|&,;',
wordWrapBreakBeforeCharacters: '{([+',
wordWrapBreakObtrusiveCharacters: '.',
wordWrapColumn: 80,
wordWrapMinified: true,
wrappingIndent: 'none',
// minimap: {
// autohide: true,
// },
}}
onChange={setSqlValue}
/>
);
return (
<Modal
title={null}
simple
visible={showModal === 'export'}
autoFocus={false}
onOk={() => copy()}
okText={t('Copy')}
cancelText={t('Close')}
onCancel={() => onCloseModal()}
style={{ width: 'auto' }}
>
<h5 className="text-[20px] py-[10px] font-bold">{t('Export ERD Data Model')}</h5>
<Tabs
activeTab={exportType}
onChange={val => setExportType(val)}
className="ring-2 ring-[#359c899a] p-0 w-[680px]"
>
<TabPane key="dbml" title="DBML">
{editor}
</TabPane>
<TabPane key="postgres" title="PostgreSQL">
{editor}
</TabPane>
<TabPane key="mysql" title="MySQL">
{editor}
</TabPane>
<TabPane key="mssql" title="MSSQL">
{editor}
</TabPane>
</Tabs>
</Modal>
);
}
import { Checkbox, Form, Input, Space, Tag, Modal, AutoComplete } from '@arco-design/web-react';
import fieldTypes from '../data/filed_typs';
import graphState from '../hooks/use-graph-state';
import tableModel from '../hooks/table-model';
import { useTranslation } from 'react-i18next';
/**
* It renders a form for editing a table
* @param props - The props passed to the component.
* @returns A TableForm component
*/
export default function FieldForm(props) {
const [form] = Form.useForm();
const { formChange, onFormChange } = props;
const { editingField, setEditingField, addingField, setAddingField } =
graphState.useContainer();
const { updateTable, removeField } = tableModel();
const { field, table } = editingField;
const save = values => {
const data = { ...field, ...values };
table.fields = table.fields.map(f => (f.id === data.id ? data : f));
updateTable(table);
};
const { t } = useTranslation('graph');
return table ? (
<Modal
title={
<div style={{ textAlign: 'left' }}>
{t('Edit')}
{table ? (
<Tag color="arcoblue" style={{ margin: '0 4px' }}>
{table.name}
</Tag>
) : (
''
)}
{t('Field')}
</div>
}
visible={!!table}
onCancel={() => {
if (addingField?.index) {
removeField(addingField.table, addingField.index);
}
setEditingField({});
}}
onOk={() => {
setAddingField(null);
form.submit();
}}
escToExit={!formChange}
maskClosable={!formChange}
afterClose={() => {
onFormChange(false);
}}
afterOpen={() => {
form.resetFields();
}}
style={{ width: 580 }}
okText="Commit"
cancelText={t('Cancel')}
>
{field && (
<Form
onSubmit={save}
form={form}
labelAlign="left"
requiredSymbol={false}
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
onValuesChange={(changedValues, allValues) => {
if (!formChange) onFormChange(true);
}}
>
<Space direction="vertical" style={{ width: '100%' }}>
<Space className="table-form-item">
<Form.Item
label={t('Name')}
field="name"
initialValue={field.name}
rules={[
{
required: true,
message: t('Please enter field name'),
},
{
validator: (value, cb) => {
return table.fields
.filter(item => item.id !== field.id)
.find(item => item.name === value)
? cb(t('have same name field'))
: cb();
},
},
]}
>
<Input allowClear placeholder={t('Name')} />
</Form.Item>
<Form.Item
label={t('Type')}
field="type"
initialValue={field.type}
rules={[
{
required: true,
message: t('Please choose field type'),
},
]}
>
<AutoComplete
data={fieldTypes}
placeholder={t('Type')}
></AutoComplete>
</Form.Item>
</Space>
<Space className="table-form-item">
<Form.Item
label={t('Comment')}
field="note"
initialValue={field.note || ''}
>
<Input allowClear placeholder={t('Comment')} />
</Form.Item>
<Form.Item
label={t('Default')}
field="dbdefault"
initialValue={field.dbdefault || ''}
>
<Input allowClear placeholder={t('Default')} />
</Form.Item>
</Space>
<Space className="table-form-item">
<Form.Item noStyle field="pk" initialValue={field.pk}>
<Checkbox defaultChecked={field.pk}>Primary</Checkbox>
</Form.Item>
<Form.Item noStyle field="unique" initialValue={field.unique}>
<Checkbox defaultChecked={field.unique}>Unique</Checkbox>
</Form.Item>
<Form.Item noStyle field="not_null" initialValue={field.not_null}>
<Checkbox defaultChecked={field.not_null}>Not Null</Checkbox>
</Form.Item>
<Form.Item noStyle field="increment" initialValue={field.increment}>
<Checkbox defaultChecked={field.increment}>Increment</Checkbox>
</Form.Item>
</Space>
</Space>
</Form>
)}
</Modal>
) : null;
}
import { Modal, Notification, Select, Tabs } from '@arco-design/web-react';
import { Parser } from '@dbml/core';
import { useRef, useState } from 'react';
import { nanoid } from 'nanoid';
import Editor from '@monaco-editor/react';
import graphState from '../hooks/use-graph-state';
import tableModel from '../hooks/table-model';
import { Typography } from '@arco-design/web-react';
import { map } from 'lodash';
import { sortBy } from 'lodash';
import { useEffect } from 'react';
import { useMemo } from 'react';
import ConnectDb from '@/client/api/connectDb';
import useSWRMutation from 'swr/mutation';
const TabPane = Tabs.TabPane;
import { Form, Input, Button, InputNumber } from '@arco-design/web-react';
import { Grid } from '@arco-design/web-react';
import { Message } from '@arco-design/web-react';
import { useTranslation } from 'react-i18next';
const FormItem = Form.Item;
export function DBForm({
form,
slot = null,
initialValues = {
client: 'mysql2',
host: '139.198.179.193',
port: 32094,
user: 'root',
password: '123789',
database: 'xyy',
},
}) {
const [loading, setLoading] = useState(false);
const { trigger, isMutating } = useSWRMutation('ConnectDb', (_key, { arg }) => {
setLoading(true);
ConnectDb.create(arg)
.then(
({ data }) => {
if (data?.status === 200) {
Message.success(t('Connection Succeeded'));
} else {
Message.error(t('Connection Failed'));
}
},
() => {
Message.error(t('Connection Failed'));
}
)
.finally(() => {
setLoading(false);
});
});
const { t } = useTranslation('modal');
return (
<Form
form={form}
style={{ width: '100%' }}
initialValues={initialValues}
autoComplete="off"
onValuesChange={(v, vs) => {}}
onSubmit={v => {}}
className="p-[20px]"
>
<FormItem label={t('Connection Information')} rules={[{ required: true }]}>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<Form.Item field="host" rules={[{ required: true }]} className="mb-0">
<Input placeholder={t('Please Input IP')} />
</Form.Item>
</Grid.Col>
<Grid.Col span={12}>
<Form.Item field="port" rules={[{ required: true }]} className="mb-0">
<Input placeholder={t('Please Input Port')} />
</Form.Item>
</Grid.Col>
</Grid.Row>
</FormItem>
<Form.Item label={t('Database Type')} field="client" rules={[{ required: true }]}>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<Form.Item field="client" rules={[{ required: true }]} className="mb-0">
<Select
allowClear
placeholder={t('Please Select')}
options={[
{
label: 'mysql',
value: 'mysql2',
},
]}
></Select>
</Form.Item>
</Grid.Col>
<Grid.Col span={12}>
<Form.Item field="database" rules={[{ required: true }]} className="mb-0">
<Input placeholder={t('Please Input Database Name')} />
</Form.Item>
</Grid.Col>
</Grid.Row>
</Form.Item>
<FormItem label={t('Account')} field="user" rules={[{ required: true }]}>
<Input placeholder={t('Please Input Username')} />
</FormItem>
<FormItem label={t('Password')} field="password" rules={[{ required: true }]}>
<Input placeholder={t('Please Input Database Password')} type="password" />
</FormItem>
{slot}
<FormItem wrapperCol={{ offset: 5 }}>
<Button
style={{ marginRight: 24 }}
onClick={() => {
form.resetFields();
}}
>
{t('Clear')}
</Button>
<Button
type="primary"
style={{ marginRight: 24 }}
loading={loading}
onClick={() => {
trigger(form.getFields());
}}
>
{t('Test Connection')}
</Button>
</FormItem>
</Form>
);
}
function bfs(nodes) {
const oldNodes = Object.fromEntries(nodes.map(v => [v.id, v]));
const visited = [];
const nodeMap = new Map();
let queue = [];
// 广度优先搜索
for (let i = 0; i < nodes.length; i++) {
if (!nodeMap.has(nodes[i].id)) {
nodeMap.set(nodes[i].id, nodes[i]);
queue.push(nodes[i]);
while (queue.length > 0) {
const currNode = queue.shift();
visited.push(currNode);
// 遍历当前节点的所有关联节点
for (const endpoint of currNode.endpoints) {
for (const refNode of endpoint) {
if (!nodeMap.has(refNode.ref)) {
queue.push(oldNodes[refNode.ref]);
queue = sortBy(queue, o => o.raw.relateLength * 1);
nodeMap.set(refNode.ref, oldNodes[refNode.ref]);
}
}
}
}
}
}
return map(visited, ({ raw }) => raw);
}
/**
* It's a modal that allows you to import a graph from a string
* @returns Modal component
*/
export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, type, importDBML }) {
const { t } = useTranslation('modal');
const { theme, setTableDict, setLinkDict, tableList } = graphState.useContainer();
const { calcXY } = tableModel();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const { trigger, isMutating, data } = useSWRMutation('getDbDBML', (_key, { arg }) => {
setLoading(true);
return ConnectDb.getDbDBML(arg)
.then(
({ data }) => {
if (data?.status === 200) {
setValue(data?.data);
setImportType('dbml');
return data?.data;
} else {
Message.error();
}
},
() => {
Message.error();
}
)
.finally(() => {
setLoading(false);
});
});
useEffect(() => {
if (type === 1) {
setImportType('dbml');
} else if (type === 2) {
setImportType('postgres');
} else if (type === 3) {
setImportType('db');
}
}, [type]);
const [val, setValue] = useState('');
const [importType, setImportType] = useState('dbml');
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleOk = async (changeValue, handleCb, failCall) => {
const value = changeValue || val;
if (!value) {
onCloseModal();
return;
}
try {
const result = await Parser.parse(value, importType);
const graph = result.schemas[0];
const tableDict = {};
const linkDict = {};
const tables = [...tableList];
const tableNameMap = Object.fromEntries(graph.tables.map(v => [v.name, v]));
bfs(
sortBy(
graph.tables.map(v => {
let relateLength = 0;
return {
endpoints: v.fields
.map(t => {
relateLength += t.endpoints.length;
return t.endpoints.map(p => ({
id: p.id,
fieldNames: p.fieldNames,
tableName: p.tableName,
ref: tableNameMap[
p.ref.endpoints.filter(
port => port.tableName !== v.name
)[0].tableName
].id,
}));
})
.filter(item => item.length),
id: v.id,
raw: v,
relateLength: relateLength,
};
}),
o => o.relateLength * -1
)
).forEach((table, _index) => {
const id = nanoid();
const [x, y] = calcXY(0, tables);
const newTable = {
id,
name: table.name,
note: table.note,
x,
y,
fields: table.fields.map(field => {
const fieldId = nanoid();
return {
id: fieldId,
increment: field.increment,
name: field.name,
not_null: field.not_null,
note: field.note,
pk: field.pk,
unique: field.unique,
type: field.type.type_name.toUpperCase(),
};
}),
};
tableDict[id] = newTable;
tables.push(newTable);
});
graph.refs.forEach(ref => {
const id = nanoid();
linkDict[id] = {
id,
endpoints: ref.endpoints.map(endpoint => {
const table = Object.values(tableDict || {}).find(
table => table.name === endpoint.tableName
);
return {
id: table.id,
relation: endpoint.relation,
fieldId: table.fields.find(
field => field.name === endpoint.fieldNames[0]
).id,
};
}),
};
});
setTableDict(state => ({
...state,
...tableDict,
}));
setLinkDict(state => ({
...state,
...linkDict,
}));
setValue('');
onCloseModal();
handleCb ? handleCb({ tableDict, linkDict }) : cb({ tableDict, linkDict });
} catch (e) {
console.log(e);
Notification.error({
title: 'Parse failed',
});
failCall && failCall();
}
};
if (importDBML) {
importDBML.current = handleOk;
}
const recordData = useRef();
useEffect(() => {
if (recordData.current !== data) {
handleOk();
recordData.current = data;
}
}, [data]);
const editor = (
<Editor
className={`!mt-0 ${theme === 'dark' ? 'bg-[#1e1e1e]' : ' bg-[#fff]'} mt-[10px]`}
language={importType === 'dbml' ? 'apex' : 'sql'}
width="680px"
height="60vh"
theme={theme === 'dark' ? 'vs-dark' : 'light'}
options={{
acceptSuggestionOnCommitCharacter: true,
acceptSuggestionOnEnter: 'on',
accessibilitySupport: 'auto',
autoIndent: false,
automaticLayout: true,
codeLens: true,
colorDecorators: true,
contextmenu: true,
cursorBlinking: 'blink',
cursorSmoothCaretAnimation: false,
cursorStyle: 'line',
disableLayerHinting: false,
disableMonospaceOptimizations: false,
dragAndDrop: false,
fixedOverflowWidgets: false,
folding: true,
foldingStrategy: 'auto',
fontLigatures: false,
formatOnPaste: false,
formatOnType: false,
hideCursorInOverviewRuler: false,
highlightActiveIndentGuide: true,
links: true,
mouseWheelZoom: false,
multiCursorMergeOverlapping: true,
multiCursorModifier: 'alt',
overviewRulerBorder: true,
overviewRulerLanes: 2,
quickSuggestions: true,
quickSuggestionsDelay: 100,
readOnly: false,
renderControlCharacters: false,
renderFinalNewline: true,
renderIndentGuides: true,
renderLineHighlight: 'line',
renderWhitespace: 'none',
revealHorizontalRightPadding: 300,
roundedSelection: true,
rulers: [],
scrollBeyondLastColumn: 5,
scrollBeyondLastLine: true,
selectOnLineNumbers: true,
selectionClipboard: true,
selectionHighlight: true,
showFoldingControls: 'mouseover',
smoothScrolling: false,
suggestOnTriggerCharacters: true,
wordBasedSuggestions: true,
wordSeparators: '~!@#$%^&*()-=+[{]}|;:\'",.<>/?',
wordWrap: 'wordWrapColumn',
wordWrapBreakAfterCharacters: '\t})]?|&,;',
wordWrapBreakBeforeCharacters: '{([+',
wordWrapBreakObtrusiveCharacters: '.',
wordWrapColumn: 80,
wordWrapMinified: true,
wrappingIndent: 'none',
// minimap: {
// autohide: true,
// },
}}
onChange={setValue}
/>
);
const tabs = useMemo(() => {
const result = [];
if (type === 1 || !type) {
result.push(
<TabPane key="dbml" title="DBML" className="pt-0">
{editor}
</TabPane>
);
} else if (type === 2 || !type) {
result.push(
<TabPane key="postgres" title="PostgreSQL">
{editor}
</TabPane>,
<TabPane key="mysql" title="MySQL">
{editor}
</TabPane>,
<TabPane key="mssql" title="MSSQL">
{editor}
</TabPane>
);
} else if (type === 3 || !type) {
result.push(
<TabPane key="db" title="db">
<DBForm form={form} />
</TabPane>
);
}
return result;
}, [type]);
return (
<Modal
title={null}
simple
visible={showModal === 'import'}
autoFocus={false}
onOk={() => (type === 3 ? trigger(form.getFields()) : handleOk())}
okText={t('Import')}
cancelText={t('Close')}
onCancel={() => onCloseModal()}
style={{ width: 'auto' }}
unmountOnExit
>
<h5 className="text-[20px] py-[10px] font-bold">{t('Import ERD Data Model')}</h5>
<Tabs
activeTab={importType}
onChange={val => setImportType(val)}
className="ring-2 ring-[#359c899a] p-0 w-[680px]"
>
{tabs}
</Tabs>
</Modal>
);
}
import { Modal, Button, Space, Popconfirm } from '@arco-design/web-react';
import graphState from '../hooks/use-graph-state';
import { useTranslation } from 'react-i18next';
/**
* It renders a modal that allows the user to change the relation of a link or delete the link
* @param props - { editingLink, setEditingLink, setLinkDict }
* @returns Modal component
*/
export default function LinkModal(props) {
const { editingLink, setEditingLink } = props;
const { setLinkDict } = graphState.useContainer();
const { t } = useTranslation('linkModal');
const changeRelation = relation => {
const { linkId, fieldId } = editingLink;
setLinkDict(state => {
return {
...state,
[linkId]: {
...state[linkId],
endpoints: state[linkId].endpoints.map(endpoint => {
if (endpoint.fieldId === fieldId) {
return {
...endpoint,
relation,
};
}
if (relation === '*' && endpoint.fieldId !== fieldId) {
return {
...endpoint,
relation: '1',
};
}
return endpoint;
}),
},
};
});
setEditingLink(null);
};
const removeLink = () => {
const { linkId, fieldId } = editingLink;
setLinkDict(state => {
delete state[linkId];
return { ...state };
});
setEditingLink(null);
};
return (
<Modal
title="Link"
visible={!!editingLink}
onCancel={() => setEditingLink(null)}
footer={null}
autoFocus={false}
focusLock={false}
>
<Space
style={{
width: '100%',
justifyContent: 'space-between',
}}
>
<Space>
<label>{t('Change relation')}:</label>
<Button
type="primary"
onClick={() => {
changeRelation('1');
}}
>
{t('startPoint')}
</Button>
<Button
type="primary"
onClick={() => {
changeRelation('*');
}}
>
{t('endpoint')}
</Button>
</Space>
<Popconfirm
title="Are you sure to delete this path?"
onOk={() => {
removeLink();
}}
>
<Button>{t('Delete Path')}</Button>
</Popconfirm>
</Space>
</Modal>
);
}
import graphState from '../hooks/use-graph-state';
import { tableWidth, fieldHeight, commentHeight, titleHeight } from '../data/settings';
const control = 20;
const padding = 5;
const gripWidth = 10;
const gripRadius = gripWidth / 2;
const margin = 0.5;
/**
* It takes a link object and returns a path element that connects the two tables
* @param props - The props object that is passed to the component.
* {
* link,
* setEditingLink,
* isActive
* }
* @returns A svg path is being returned.
*/
export default function LinkPath(props) {
const { link, setEditingLink, isActive } = props;
const { tableDict, version } = graphState.useContainer();
const editable = version === 'currentVersion';
if (!tableDict) return null;
const { endpoints } = link;
const [sourceTable, targetTable] = [tableDict[endpoints[0].id], tableDict[endpoints[1].id]];
const [sourceFieldIndex, targetFieldIndex] = [
sourceTable.fields.findIndex(field => field.id === endpoints[0].fieldId),
targetTable.fields.findIndex(field => field.id === endpoints[1].fieldId),
];
const calcHeight = titleHeight + commentHeight + fieldHeight / 2;
const sourceFieldPosition = {
x: sourceTable.x,
y: sourceTable.y + sourceFieldIndex * fieldHeight + calcHeight,
...endpoints[0],
};
const targetFieldPosition = {
x: targetTable.x,
y: targetTable.y + targetFieldIndex * fieldHeight + calcHeight,
...endpoints[1],
};
// const [source, target] = [sourceFieldPosition, targetFieldPosition];
const [source, target] = [sourceFieldPosition, targetFieldPosition].sort((a, b) => {
return a.x - b.x || a.y - b.y;
});
const sourceLeft = source.x + padding + gripRadius + margin;
const sourceRight = source.x + tableWidth - padding - gripRadius - margin;
let x = sourceLeft;
const y = source.y + gripRadius + margin;
const targetLeft = target.x + padding + gripRadius + margin;
const targetRight = target.x + tableWidth - padding - gripRadius - margin;
let minDistance = Math.abs(sourceLeft - targetLeft);
let x1 = targetLeft;
[
[sourceLeft, targetRight],
[sourceRight, targetLeft],
[sourceRight, targetRight],
].forEach(items => {
if (Math.abs(items[0] - items[1]) < minDistance) {
minDistance = Math.min(items[0] - items[1]);
x = items[0];
x1 = items[1];
}
});
const y1 = target.y + gripRadius + margin;
const midX = x1 - (x1 - x) / 2;
const midY = y1 - (y1 - y) / 2;
const handlerContextMenu = e => {
e.preventDefault();
e.stopPropagation();
};
let d = `M ${x} ${y}
C ${x + control} ${y} ${midX} ${midY} ${midX} ${midY}
C ${midX} ${midY} ${x1 - control} ${y1} ${x1} ${y1}`;
let foreignObjectPositions = [
{
x: (x + control + midX) / 2 - 10,
y: (y + midY) / 2 - 10,
},
{ x: (x1 - control + midX) / 2 - 10, y: (y1 + midY) / 2 - 10 },
];
if (endpoints[0].id == endpoints[1].id) {
const factor = (y1 - y) / 50 < 2 ? 2 : (y1 - y) / 50;
d = `M ${sourceRight} ${y}
L ${sourceRight + control} ${y}
C ${sourceRight + control * factor} ${y} ${sourceRight + control * factor} ${y1} ${
sourceRight + control
} ${y1}
L ${sourceRight} ${y1}`;
foreignObjectPositions = [
{
x: 0,
y: 0,
},
{ x: targetRight + control - 10, y: y1 - 10 },
];
}
return (
<>
<path
d={d}
stroke="black"
// strokeWidth="2"
fill="none"
className={`path-line hover:stroke-[var(--pc)] hover:stroke-[5px] ${
isActive ? 'stroke-[var(--pc)] stroke-[5px]' : ''
}`}
/>
<foreignObject
x={foreignObjectPositions[0].x}
y={foreignObjectPositions[0].y}
width={20}
height={20}
onMouseDown={() => {
if (!editable) return;
setEditingLink({
linkId: link.id,
fieldId: source.fieldId,
});
}}
onContextMenu={handlerContextMenu}
>
<div
style={{
cursor: editable ? 'pointer' : 'default',
userSelect: 'none',
}}
className="path-label"
>
{source.relation}
</div>
</foreignObject>
<foreignObject
x={foreignObjectPositions[1].x}
y={foreignObjectPositions[1].y}
width={20}
height={20}
onMouseDown={() => {
if (!editable) return;
setEditingLink({
linkId: link.id,
fieldId: target.fieldId,
});
}}
onContextMenu={handlerContextMenu}
>
<div
style={{
cursor: editable ? 'pointer' : 'default',
userSelect: 'none',
}}
className="path-label"
>
{target.relation}
</div>
</foreignObject>
</>
);
}
import { Space, Button, Switch, Notification } from '@arco-design/web-react';
import { IconSunFill, IconMoonFill, IconThunderbolt, IconRobot } from '@arco-design/web-react/icon';
import Link from 'next/link';
import graphState from '../hooks/use-graph-state';
import { Dropdown } from '@arco-design/web-react';
import { Menu } from '@arco-design/web-react';
import { useTranslation } from 'react-i18next';
import SwitchLang from './switchI18n';
import AI from './AITool';
import { useMemo, useState } from 'react';
import { map, uniqueId } from 'lodash';
import { useRouter } from 'next/router';
import { ADD_SCHEMA } from '@/data/prompt';
/**
* It renders a nav bar with a link to the home page, a button to add a new graph, and a dropdown menu
* with a list of import options
* @param props - the props passed to the component
* @returns List Nav component
*/
export default function ListNav({
importGraph,
addGraph,
customNode,
addExample,
importDBML,
handlerImportGraph,
}) {
const { theme, setTheme } = graphState.useContainer();
const { t } = useTranslation('ListNav');
const router = useRouter();
const custom = customNode
? customNode
: [
<Dropdown
key={0}
trigger={'hover'}
droplist={
<Menu>
<Menu.Item key="3" onClick={() => importGraph(3)}>
{t('Import Database')}
</Menu.Item>
<Menu.Item key="0" onClick={() => importGraph(1)}>
{t('Import DBML')}
</Menu.Item>
<Menu.Item key="2" onClick={() => importGraph(2)}>
{t('Import DDL')}
</Menu.Item>
<Menu.Item key="1" onClick={() => setSimpleModeVisible(true)}>
{/* <span className=" text-lime-600 mr-[10px]">
<IconRobot />
</span> */}
{t('Created by AI')}
</Menu.Item>
<Menu.Item key="1" onClick={() => addGraph()}>
{t('Create from Blank')}
</Menu.Item>
</Menu>
}
>
<Button type="primary" shape="round" className="shadow">
+ {t('New Model')}
</Button>
</Dropdown>,
<Button
key={1}
size="small"
shape="round"
className="shadow"
onClick={() => addExample()}
>
{t('Import Example')}
</Button>,
];
const [messageList, setMessageList] = useState([
{
role: 'system',
content:
'IMPRTANT: You are a virtual assistant powered by the gpt-3.5-turbo model, now time is 2023/5/30 16:57:14}',
},
{
role: 'user',
content: ADD_SCHEMA(),
},
{
role: 'assistant',
content: '好的,我明白了。请问你的业务是什么?',
},
]);
const [simpleModeVisible, setSimpleModeVisible] = useState(false);
return (
<div className="nav">
<AI
startView={3}
messageList={messageList}
setMessageList={() => false}
simpleMode
simpleModeVisible={simpleModeVisible}
setSimpleModeVisible={setSimpleModeVisible}
doneFx={message => {
const elNode = document.createElement('div');
elNode.innerHTML = message;
let sqlNodes = elNode.querySelectorAll('dbml');
let modelName = elNode.querySelector('modelName');
if (!sqlNodes.length) {
let str = message.replace(/```dbml/g, `<dbml>`);
str = str.replace(/```/g, `</sql>`);
elNode.innerHTML = str;
sqlNodes = elNode.querySelectorAll('dbml');
}
if (!sqlNodes[0]?.textContent) {
Notification.error({
title: t(
'AI was unable to understand your input, please further clarify your input.'
),
});
}
importDBML.current(sqlNodes[0]?.textContent, ({ tableDict, linkDict }) => {
handlerImportGraph({
tableDict,
linkDict,
name: `${modelName.outerText}`,
});
});
}}
// renderMessageItem={props => <MessageItem {...props} />}
/>
<div>
<Link href="/graphs" passHref>
<strong className="text-[22px] app-text mr-[10px]">CHAT QUERY</strong>
<span className="text-[var(--pc)]">
{t('Data Query Based on Data Model and AI')}
</span>
</Link>
</div>
<Space>
{custom}
<SwitchLang />
<Switch
className="shadow"
checkedIcon={<IconMoonFill />}
uncheckedIcon={<IconSunFill className="text-orange-500 " />}
checked={theme === 'dark'}
onChange={e => setTheme(e ? 'dark' : 'light')}
/>
</Space>
</div>
);
}
import { useState, useEffect } from 'react';
import { Drawer, Notification, Popconfirm, Space } from '@arco-design/web-react';
import { delLogs, getLogs } from '../data/db';
import graphState from '../hooks/use-graph-state';
import tableModel from '../hooks/table-model';
import { useTranslation } from 'react-i18next';
export default function LogsDrawer({ showDrawer, onCloseDrawer }) {
const { id, version } = graphState.useContainer();
const { applyVersion } = tableModel();
const [logs, setLogs] = useState(undefined);
const { t } = useTranslation();
const viewLogs = async () => {
const records = await getLogs(id);
setLogs(records);
};
useEffect(() => {
if (showDrawer === 'logs') {
viewLogs();
}
}, [showDrawer]);
const deleteLogs = (e, id) => {
e.preventDefault();
e.stopPropagation();
delLogs(id);
setLogs(state => state.filter(item => item.id !== id));
Notification.success({
title: 'Delete logs record success',
});
};
return (
<Drawer
width={320}
title="Logs Record"
visible={showDrawer === 'logs'}
autoFocus={false}
footer={null}
mask={false}
onCancel={() => onCloseDrawer()}
style={{ boxShadow: '0 0 8px rgba(0, 0, 0, 0.1)' }}
>
<Space
align="start"
className={`custom-radio-card ${
version === 'currentVersion' ? 'custom-radio-card-checked' : ''
}`}
onClick={() => applyVersion('currentVersion')}
>
<div className="custom-radio-card-dot"></div>
<div>
<div className="custom-radio-card-title">Current Version</div>
</div>
</Space>
{logs
? logs.map(item => (
<Space
key={item.updatedAt}
align="start"
className={`custom-radio-card ${
version === item.updatedAt ? 'custom-radio-card-checked' : ''
}`}
onClick={() => applyVersion(item)}
>
<div className="custom-radio-card-dot"></div>
<div>
<div className="custom-radio-card-title overflow-hidden text-ellipsis whitespace-nowrap w-[200px]">
Version {item.id}
</div>
<div className="custom-radio-card-secondary">
Auto save at {new Date(item.updatedAt).toLocaleString()}
</div>
</div>
<Popconfirm
position="br"
title={t('Are you sure you want to delete this logs record?')}
okText={t('Yes')}
cancelText={t('No')}
onOk={e => deleteLogs(e, item.id)}
onCancel={e => {
e.preventDefault();
e.stopPropagation();
}}
>
<a
className="delete-btn text-red-500 hover:text-red-800"
onClick={e => {
e.preventDefault();
e.stopPropagation();
}}
>
[{t('Delete')}]
</a>
</Popconfirm>
</Space>
))
: null}
</Drawer>
);
}
import { Button, Space, Popconfirm, Input, Switch, Dropdown, Menu } from '@arco-design/web-react';
import {
IconSunFill,
IconMoonFill,
IconLeft,
IconRobotAdd,
IconPlus,
IconImport,
} from '@arco-design/web-react/icon';
import Link from 'next/link';
import graphState from '../hooks/use-graph-state';
import tableModel from '../hooks/table-model';
import { useTranslation } from 'react-i18next';
import SwitchLang from './switchI18n';
import { useRouter } from 'next/router';
import { Drawer } from '@arco-design/web-react';
import { useState } from 'react';
import AI from '@/components/AITool';
import { useEffect } from 'react';
import exportSQL from 'utils/export-sql';
import { useMemo } from 'react';
import { get, map } from 'lodash';
import { Alert } from '@arco-design/web-react';
import { Descriptions } from '@arco-design/web-react';
import { useRef } from 'react';
import { Tag } from '@arco-design/web-react';
import { ADD_TABLE } from '@/data/prompt/index';
/**
* It renders a nav bar with a title, a save button, a demo button, a clear button, an export button,
* and a name input
* @param props - the props passed to the component
* @returns A Nav component that takes in a title, a save button, a demo button, a clear button, an export button
*/
const COLORS = [
'orangered',
'orange',
'gold',
'lime',
'green',
'cyan',
'blue',
'arcoblue',
'purple',
'pinkpurple',
'magenta',
'gray',
];
function MessageItem({ message, role, rawRender }) {
const { t } = useTranslation('nav');
const { sql, params, tableName } = useMemo(() => {
const elNode = document.createElement('div');
elNode.innerHTML = message;
let sqlNodes = elNode.querySelectorAll('dbml');
if (!sqlNodes.length) {
let str = message.replace(/```dbml/g, `<dbml>`);
str = str.replace(/```/g, `</sql>`);
elNode.innerHTML = str;
sqlNodes = elNode.querySelectorAll('dbml');
}
const tableNames = elNode.querySelectorAll('tableName');
const varNodes = elNode.querySelectorAll('field');
const varDescriptionNodes = elNode.querySelectorAll('description');
return {
sql: map(sqlNodes, node => node.outerText?.trim()),
params: map(varNodes, (node, index) => {
return {
label: node.outerText?.trim(),
value: get(varDescriptionNodes, index)?.outerText?.trim(),
};
}),
tableName: [...tableNames].map((v, i) => (
<Tag key={v} color={COLORS[i]} className="mr-[5px] font-bold">
{v.outerText}
</Tag>
)),
};
}, [message]);
if (role === 'user') {
return rawRender({ message, role });
}
console.log(params, 'params');
return (
<div className="mt-[20px]">
{sql.length ? (
<>
<Alert
style={{ marginBottom: 20 }}
type="success"
title={t(
'According to your business needs, a new database model has been generated for you.'
)}
content={
tableName ? (
<div>
{' '}
{t('Successfully added the following database tables')}:{' '}
{tableName}
</div>
) : undefined
}
/>
<Descriptions
border
column={1}
title={t('Table field information')}
data={params}
style={{ marginBottom: 20 }}
labelStyle={{ paddingRight: 36 }}
/>
</>
) : (
<Alert
type="error"
content={t('Generation failed, please modify the requirements and try again.')}
/>
)}
</div>
);
}
export default function Nav({ setShowModal, setShowDrawer, leave = () => {}, importDBML }) {
const {
tableDict,
linkDict,
name,
setName,
theme,
setTheme,
setTableDict,
setLinkDict,
version,
} = graphState.useContainer();
const sql = exportSQL(tableDict, linkDict, 'dbml');
const { updateGraph, addTable, applyVersion } = tableModel();
const { t } = useTranslation('nav');
const router = useRouter();
const [showAIModal, setShowAIModal] = useState(false);
const [messageList, setMessageList] = useState([]);
const [saveLoading, setSaveLoading] = useState(false);
useEffect(() => {
setMessageList([
{
role: 'system',
content:
'IMPRTANT: You are a virtual assistant powered by the gpt-3.5-turbo model, now time is 2023/5/30 16:57:14}',
},
{
role: 'user',
content: ADD_TABLE(sql),
},
{
role: 'assistant',
content: '好的,我明白了。请问你需要在当前数据库模型新增加的业务需求是什么?',
},
...messageList.slice(3),
]);
console.log(JSON.stringify(messageList));
}, [sql]);
if (version !== 'currentVersion') {
return (
<nav className="nav">
<div className="nav-title">Logs Record: {name}</div>
<Space>
<Button
loading={saveLoading}
onClick={async () => {
setSaveLoading(true);
await updateGraph();
setSaveLoading(false);
}}
type="primary"
status="success"
shape="round"
style={{ marginLeft: 8 }}
className="shadow"
>
{t('Apply Select Version')}
</Button>
<Button
onClick={() => applyVersion('currentVersion')}
shape="round"
style={{ marginLeft: 8 }}
className="shadow"
>
{t('Exit Logs View')}
</Button>
</Space>
</nav>
);
}
return (
<nav className="nav">
<Space>
<IconLeft
style={{ fontSize: 20 }}
onClick={() => {
leave(f => {
f && updateGraph();
router.push('/graphs');
});
}}
/>
<Input
type="text"
value={name}
className="shadow"
onChange={value => setName(value)}
style={{ width: '240px' }}
/>
<Button
size="small"
type="primary"
status="success"
shape="round"
loading={saveLoading}
className="shadow"
onClick={async () => {
setSaveLoading(true);
await updateGraph();
setSaveLoading(false);
}}
>
{t('Save')}
</Button>
</Space>
<Space>
<Dropdown
className="shadow"
position="bottom"
droplist={
<Menu>
<Menu.Item
key="add"
className="context-menu-item"
onClick={() => addTable()}
>
<IconPlus /> {t('Add Table')}
</Menu.Item>
<Menu.Item
key="add"
className="context-menu-item"
onClick={() => setShowAIModal(true)}
>
<IconRobotAdd /> {t('AI createTable')}
</Menu.Item>
<Menu.Item
key="import"
className="context-menu-item"
onClick={() => setShowModal('import')}
>
<IconImport /> {t('Import Table')}
</Menu.Item>
</Menu>
}
>
<Button size="small" type="primary" shape="round" className="shadow">
+ {t('New Table')}
</Button>
</Dropdown>
<Popconfirm
title={t('Are you sure you want to delete all the tables?')}
okText={t('Yes')}
cancelText={t('No')}
position="br"
onOk={() => {
setTableDict({});
setLinkDict({});
}}
>
<Button
size="small"
type="outline"
status="danger"
shape="round"
className="shadow"
>
{t('Clear')}
</Button>
</Popconfirm>
<Button
className="shadow"
size="small"
type="outline"
shape="round"
onClick={() => setShowModal('export')}
>
{t('Export')}
</Button>
<Button
className="shadow"
size="small"
type="secondary"
shape="round"
onClick={() => setShowDrawer('logs')}
>
{t('Logs')}
</Button>
<SwitchLang />
<Switch
className="shadow"
checkedIcon={<IconMoonFill />}
uncheckedIcon={<IconSunFill className="text-orange-500 " />}
checked={theme === 'dark'}
onChange={e => setTheme(e ? 'dark' : 'light')}
/>
</Space>
<Drawer
width={620}
title={<span>{t('AI createTable')}</span>}
visible={showAIModal}
className="shadow"
onCancel={() => setShowAIModal(false)}
footer={null}
>
<AI
startView={3}
doneFx={message => {
const elNode = document.createElement('div');
elNode.innerHTML = message;
let sqlNodes = elNode.querySelectorAll('dbml');
const Record = {
tableDict,
linkDict,
};
setTimeout(() => {
if (sqlNodes[0]?.textContent) {
setTableDict({});
setLinkDict({});
importDBML.current(sqlNodes[0]?.textContent, undefined, () => {
setTableDict(Record.tableDict);
setLinkDict(Record.linkDict);
});
}
}, 100);
}}
messageList={messageList}
setMessageList={setMessageList}
renderMessageItem={props => <MessageItem {...props} />}
/>
</Drawer>
</nav>
);
}
import { useState } from 'react';
import { Select } from '@arco-design/web-react';
const Option = Select.Option;
/**
* It renders a hidden input with the value of the selected option, and a Select component from antd
* that allows the user to select an option from a list of options, or create a new option
* @returns A SelectInput component that takes in a name, options, defaultValue, and width.
*/
export default function SelectInput({ name, options, defaultValue, width }) {
const [value, setValue] = useState(defaultValue);
const handleChange = value => {
setValue(value);
};
return (
<>
<input type="hidden" name={name} value={value} />
<Select value={value} onChange={handleChange} style={{ width }} allowCreate>
{options.map(item => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
</>
);
}
import { Dropdown, Menu, Button, Space, Trigger } from '@arco-design/web-react';
import { IconLanguage } from '@arco-design/web-react/icon';
import { useTranslation } from 'react-i18next';
const DropList = () => {
const { i18n } = useTranslation();
return (
<Menu
onClickMenuItem={key => i18n.changeLanguage(key)}
style={{ marginBottom: -4 }}
mode="popButton"
tooltipProps={{ position: 'left' }}
hasCollapseButton
>
<Menu.Item key="zh" className="text-[12px]">
中文
</Menu.Item>
<Menu.Item key="en" className="text-[12px]">
en
</Menu.Item>
</Menu>
);
};
export default function SwitchLang() {
return (
<Trigger
popup={() => <DropList />}
trigger={['click', 'hover']}
clickToClose
position="top"
>
<div className={`button-trigger`}>
<Button type="primary" shape="round" size="small" className="shadow">
<IconLanguage />
</Button>
</div>
</Trigger>
);
}
import { useState } from 'react';
import { Button, Popover, Space, Tag, Popconfirm, Input } from '@arco-design/web-react';
import {
IconEdit,
IconDelete,
IconMessage,
IconPlus,
IconMinus,
IconPalette,
} from '@arco-design/web-react/icon';
import { themes, tableWidth, titleHeight, commentHeight, fieldHeight } from '../data/settings';
import graphState from '../hooks/use-graph-state';
import tableModel from '../hooks/table-model';
import { useTranslation } from 'react-i18next';
const RenderTableTips = ({ field }) => (
<div className="table-tips">
<div className="head">
<span className="field-name">{field.name}</span>
<span className="field-type">{field.type}</span>
</div>
<div className="content">
<Space wrap size={4}>
{field.pk && (
<Tag size="small" color="gold">
PRIMARY
</Tag>
)}
{field.unique && (
<Tag size="small" color="green">
UNIQUE
</Tag>
)}
{field.not_null && (
<Tag size="small" color="magenta">
NOT NULL
</Tag>
)}
{field.increment && (
<Tag size="small" color="lime">
INCREMENT
</Tag>
)}
</Space>
<div className="field-item dbdefault">
<span>DEFAULT:</span>
{field.dbdefault || <span className="empty-value">(Unset)</span>}
</div>
<div className="field-item note">
<span>COMMENT:</span>
{field.note || <span className="empty-value">(No Comment)</span>}
</div>
</div>
</div>
);
/**
* It renders a table with a title, a list of fields, and a button to edit the table
* @param props - {
* table,
* onTableMouseDown,
* onGripMouseDown,
* }
* @returns A table component with a title and a list of fields.
*/
export default function Table(props) {
const {
table,
onTableMouseDown,
onGripMouseDown,
tableSelectedId,
setTableSelectId,
setCurrentHoverTable = () => {},
setCurrentHoverField = () => {},
} = props;
const { version, setEditingField, setEditingTable } = graphState.useContainer();
const { updateTable, removeTable, addField, removeField } = tableModel();
const [note, setNote] = useState(table.note);
const { t } = useTranslation('table');
const editable = version === 'currentVersion';
const handlerContextMenu = e => {
e.preventDefault();
e.stopPropagation();
};
// 12: box-shadow
const height = table.fields.length * fieldHeight + titleHeight + commentHeight + 12;
return (
<foreignObject
x={table.x}
y={table.y}
width={tableWidth}
height={height}
key={table.id}
onMouseDown={e => onTableMouseDown(e, table)}
onContextMenu={handlerContextMenu}
>
<div
className={`table shadow ${editable ? 'editable' : ''} ${
tableSelectedId === table.id ? 'table-selected' : ''
}`}
style={{ borderColor: table.theme }}
onMouseOver={() => {
setTableSelectId(table.id);
}}
onMouseOut={() => setTableSelectId(null)}
>
<div
className="table-title"
style={{
background: table.theme,
height: titleHeight,
lineHeight: `${titleHeight}px`,
}}
>
<span className="table-name">{table.name}</span>
{editable && (
<Space size={4} className="table-settings">
<Button
size="mini"
onClick={() => setEditingTable(table)}
icon={<IconEdit />}
/>
<Popconfirm
icon={null}
position="tr"
style={{ width: 480 }}
title={
<Space>
<label>{t('Comment')}:</label>
<Input
style={{ width: 240 }}
defaultValue={table.note}
allowClear
placeholder={t('Please Enter Comment')}
onChange={value => setNote(value)}
/>
</Space>
}
okText={t('Commit')}
cancelText={t('Cancel')}
onOk={() => {
updateTable({ ...props.table, note });
}}
onCancel={() => {
setNote(props.table.note);
}}
>
<Button size="mini" icon={<IconMessage />} />
</Popconfirm>
<Popover
position="tr"
title={
<>
{t('Theme')}
<Button
size="mini"
style={{
float: 'right',
fontSize: '12px',
}}
onClick={() =>
updateTable({
...props.table,
theme: undefined,
})
}
>
{t('Clear')}
</Button>
</>
}
content={
<Space
// warp
direction="vertical"
size="medium"
style={{ margin: '8px 0 4px' }}
>
{themes.map(list => (
<Space size="medium" key={list.toString()}>
{list.map(item => (
<Button
key={item}
shape="circle"
style={{
background: item,
}}
onClick={() =>
updateTable({
...props.table,
theme: item,
})
}
/>
))}
</Space>
))}
</Space>
}
trigger="click"
>
<Button size="mini" icon={<IconPalette />} />
</Popover>
<Popconfirm
position="tr"
title={t('Are you sure you want to delete this table?')}
okText={t('Yes')}
cancelText={t('No')}
onOk={() => removeTable(table.id)}
>
<Button status="danger" size="mini" icon={<IconDelete />} />
</Popconfirm>
</Space>
)}
</div>
<div
className={`table-comment ${note ? '' : 'no-comment'}`}
style={{ height: commentHeight, lineHeight: `${commentHeight}px` }}
>
{table.note || `(${t('No Comment')})`}
</div>
{table.fields &&
table.fields.map((field, index) => (
<Popover
key={field.id}
position="rt"
content={<RenderTableTips field={field} />}
>
<div
className={`row ${editable ? 'editable' : ''}`}
key={field.id}
tableid={table.id}
fieldid={field.id}
onMouseOver={() => {
setCurrentHoverField(field.id);
}}
onMouseOut={() => setCurrentHoverField(null)}
style={{ height: fieldHeight, lineHeight: `${fieldHeight}px` }}
>
<div
className="start-grip grip"
onMouseDown={onGripMouseDown}
></div>
<div className="field-content">
<div>
{field.name}
{field.pk && (
<img
alt="pk"
style={{
width: 10,
height: 10,
marginLeft: 4,
}}
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB30lEQVR4AYySg44eURxHm75MbdvmCzQso0a1bdsKatu2MTNre3fMT6c3t1gjyS+6Oef+1QpoMgVXR+4X4W/2131vEZwIs2UakjQPe5+JKo4Ttx8DKSnJuTD8aJOCmnBYso2o8tS/3wHIOdWPt/v6PWlCIMuWcFC4nLBsr8hDpOTicFIJnayD3aWkUYEoWcIyRaukJG49xEnfgpc1jmT4jPSdXWm8guCXqGATQlBDtBpbGYP5ua9o7zLK0nYNwq1F9pe/nCuAlVjfBuFlTMPPmYOjTsX41JO4sQ5lRTvur++p1d/39VGUv5pPTL8ofusn4Eni9xnYnwdivOsuYXVVBx5s7PUTwdUUiOFYxK0H+IVLML+3xfvZBz93DQXnBpNwToqyz6Gu7sCTLb2+/ONqXhpR+QUcbTLGuw44yiCCvM2839xdv7umR6ayrB3auo482977Q82W//5sipKv4moj0N+2xVWHEeRv4sOW7lXXN/Y909CgawrsmPEA61s/9Fdt8TMnC3gd77d0129t6XekUbhmBXH3NZYyAi9jLF7eat5u7Gbe2dbvYHOwFORdHP4pqHiJn7eCoPgE7zZ3N+7v7L+rJfDvgQ04Ndeu+uI0s6dnJhi/Bvn50FTrdpAEsRgA3aj/RFymC/IAAAAASUVORK5CYII="
/>
)}
{(field.unique || field.index) && (
<img
alt="index"
style={{
width: 10,
height: 10,
marginLeft: 4,
}}
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAA7UlEQVR4AW2Qg26GURQEb/rWtW3bts2otm27jdpouv39YTeeuTjH2FNdUo1a4gBB/KVaFQt+Zp9bkFJe5Yo3OKBahUJSZ60CX8KLbHMhoZIfsq1Kta5eVJel3LDIEA+kYxHeWBPwSn20ckkMQRxWXTLEEh1MMMsgzWwSS9Rh4Hs1jHJKJ0PM0U6jcDzRu4HLf7lmgUYJi5RzzLlw3EbI5o51aT3drJDwHnEaSyIJKyGnz+ijll7hxLfIRvt6vq9op4ZhlnU6ptK5Xh7oYZBF4j5jy4wzFWv6HrskfMTnG7dkRWbcJb8kvqWkGtf8ARG87I1KSSe4AAAAAElFTkSuQmCC"
/>
)}
</div>
<div className="field-type">{field.type}</div>
</div>
<div className="grip-setting">
<Button
type="secondary"
className="grip-setting-btn"
size="mini"
icon={<IconEdit />}
onClick={() => {
setEditingField({ field, table });
}}
/>
<Button
status="success"
className="grip-setting-btn"
size="mini"
icon={<IconPlus />}
onClick={() => {
addField(table, index);
}}
/>
<Popconfirm
position="tr"
title={t('Are you sure you want to delete this field?')}
okText={t('Yes')}
cancelText={t('No')}
onOk={() => removeField(table, index)}
>
<Button
status="danger"
className="grip-setting-btn"
size="mini"
icon={<IconMinus />}
/>
</Popconfirm>
</div>
</div>
</Popover>
))}
</div>
</foreignObject>
);
}
import { useState, useEffect } from 'react';
import {
Button,
Space,
Input,
Card,
Popconfirm,
Form,
Checkbox,
AutoComplete,
Drawer,
Grid,
} from '@arco-design/web-react';
import classNames from 'classnames';
import { nanoid } from 'nanoid';
import fieldTypes from '../data/filed_typs';
import tableModel from '../hooks/table-model';
import graphState from '../hooks/use-graph-state';
import { useTranslation } from 'react-i18next';
/**
* It takes the current fields array, checks if the current index is less than the length of the array,
* and if so, swaps the current index with the next index
* @param props - The props passed to the component
* @param ref - This is a reference to the form element.
* @returns A React component
*/
function TableFormItem(props) {
const { t } = useTranslation('graph');
/**
* If the index of the current field is greater than 0, then swap the current field with the field
* above it
*/
const moveUp = () => {
props.setFields(fields => {
if (props.index > 0) {
const _fields = [...fields];
[_fields[props.index], _fields[props.index - 1]] = [
_fields[props.index - 1],
_fields[props.index],
];
return _fields;
}
return fields;
});
};
/**
* It takes the current fields array, checks if the current index is less than the length of the
* array, and if so, swaps the current index with the next index
*/
const moveDown = () => {
props.setFields(fields => {
if (props.index < fields.length - 1) {
const _fields = [...fields];
[_fields[props.index], _fields[props.index + 1]] = [
_fields[props.index + 1],
_fields[props.index],
];
return _fields;
}
return fields;
});
};
const { field, fieldList } = props;
const index = `A${props.index}`;
return (
<Card
className={classNames({
dropping:
props.draggingId &&
props.droppingId === props.field.id &&
props.droppingId !== props.draggingId &&
props.index !== props.draggingIndex - 1,
dragging: props.draggingId && props.draggingId === props.field.id,
'table-form': true,
})}
draggable="true"
onDragStart={() => props.onDragStart(field.id)}
onDragEnd={() => {
props.setDraggingId(null);
props.setDroppingId(null);
}}
onDragOver={e => {
props.setDroppingId(field.id);
e.preventDefault();
}}
onDrop={e => props.onDrop(field.id)}
>
<Form.Item hidden field={`${index}.id`} initialValue={field.id}>
<Input />
</Form.Item>
<Space direction="vertical" style={{ width: '100%' }}>
<Space className="table-form-item">
<Form.Item
label={t('Name')}
field={`${index}.name`}
initialValue={field.name}
rules={[
{
required: true,
message: t('Please enter field name'),
},
{
validator: (value, cb) => {
return fieldList
.filter(item => item.id !== field.id)
.find(item => item.name === value)
? cb('have same name field')
: cb();
},
},
]}
>
<Input allowClear placeholder={t('Name1')} />
</Form.Item>
<Form.Item
label={t('Type')}
field={`${index}.type`}
initialValue={field.type}
rules={[
{
required: true,
message: t('Please choose field type'),
},
]}
>
<AutoComplete data={fieldTypes} placeholder={t('Type1')}></AutoComplete>
</Form.Item>
</Space>
<Space className="table-form-item">
<Form.Item
label={t('Comment')}
field={`${index}.note`}
initialValue={field.note || ''}
>
<Input allowClear placeholder={t('Comment')} />
</Form.Item>
<Form.Item
label={t('Default')}
field={`${index}.dbdefault`}
initialValue={field.dbdefault || ''}
>
<Input allowClear placeholder={t('Default')} />
</Form.Item>
</Space>
<Space className="table-form-item">
<Form.Item noStyle field={`${index}.pk`} initialValue={field.pk}>
<Checkbox defaultChecked={field.pk}>Primary</Checkbox>
</Form.Item>
<Form.Item noStyle field={`${index}.unique`} initialValue={field.unique}>
<Checkbox defaultChecked={field.unique}>Unique</Checkbox>
</Form.Item>
<Form.Item noStyle field={`${index}.not_null`} initialValue={field.not_null}>
<Checkbox defaultChecked={field.not_null}>Not Null</Checkbox>
</Form.Item>
<Form.Item noStyle field={`${index}.increment`} initialValue={field.increment}>
<Checkbox defaultChecked={field.increment}>Increment</Checkbox>
</Form.Item>
</Space>
<Space className="table-form-item">
<Button onClick={moveUp} type="primary" size="small" long>
↑ {t('Move up')}
</Button>
<Button onClick={moveDown} type="primary" size="small" long>
↓ {t('Move down')}
</Button>
<Popconfirm
title={t('Are you sure delete this field?')}
onOk={() => {
props.removeItem(props.field.id);
}}
okText={t('Yes')}
cancelText={t('No')}
>
<Button status="danger" size="small" long>
{t('Remove field')}
</Button>
</Popconfirm>
<Button
onClick={() => props.addItem(props.index)}
type="outline"
status="success"
size="small"
long
>
+ {t('Add field after')}
</Button>
</Space>
</Space>
</Card>
);
}
function TableBaseForm() {
const { tableList, editingTable } = graphState.useContainer();
const { removeTable } = tableModel();
const { t } = useTranslation('graph');
if (!editingTable) return null;
return (
<>
<Form.Item label={t('Table Name')} labelCol={{ span: 5 }} wrapperCol={{ span: 19 }}>
<Grid.Row align="center" gutter={8}>
<Grid.Col span={18}>
<Form.Item
field="name"
initialValue={editingTable.name}
noStyle={{ showErrorTip: true }}
rules={[
{
required: true,
message: t('Please enter table name'),
},
{
validator: (value, cb) => {
return tableList
.filter(item => item.id !== editingTable.id)
.find(item => item.name === value)
? cb(t('have same name table'))
: cb();
},
},
]}
>
<Input type="text" />
</Form.Item>
</Grid.Col>
<Grid.Col span={6}>
<Popconfirm
position="br"
title={t('Are you sure you want to delete this table?')}
okText={t('Yes')}
cancelText={t('No')}
onOk={() => removeTable(editingTable.id)}
>
<Button type="outline" status="warning">
{t('Delete table')}
</Button>
</Popconfirm>
</Grid.Col>
</Grid.Row>
</Form.Item>
<Form.Item
label={t('Table Comment')}
field="note"
labelCol={{ span: 5 }}
wrapperCol={{ span: 19 }}
initialValue={editingTable.note}
>
<Input type="text" placeholder={t('Table Comment')} />
</Form.Item>
</>
);
}
/* A forwardRef function that is used to forward the ref to the child component. */
// const TableRefFormItem = forwardRef(TableFormItem);
/**
* It renders a form for editing a table
* @param props - The props passed to the component.
* @returns A TableForm component
*/
export default function TableForm(props) {
const [fields, setFields] = useState([]);
const [form] = Form.useForm();
const { editingTable, setEditingTable } = graphState.useContainer();
const { updateTable } = tableModel();
const { t } = useTranslation('graph');
useEffect(() => {
if (editingTable) {
setFields(editingTable.fields);
}
}, [editingTable]);
const save = values => {
const { name, note, ...fields } = values;
const newTable = {
...editingTable,
name,
note,
fields: Object.values(fields),
};
updateTable(newTable);
};
const addItem = index => {
const newState = [...fields];
newState.splice(index + 1, 0, {
id: nanoid(),
name: 'new item' + newState.length,
type: '',
unique: false,
});
setFields(newState);
};
const removeItem = id => {
setFields(state => {
const fields = state.filter(item => item.id !== id);
return fields.length ? fields : [];
});
};
// Drag and drop
const [draggingId, setDraggingId] = useState(false);
const [draggingIndex, setDraggingIndex] = useState(false);
const [droppingId, setDroppingId] = useState(false);
const onDragStart = id => {
setDraggingId(id);
setDraggingIndex(fields.findIndex(item => item.id === id));
};
const onDrop = id => {
setDroppingId(null);
const index = fields.findIndex(item => item.id === id);
const draggingIndex = fields.findIndex(item => item.id === draggingId);
if (index === draggingIndex) {
return setDraggingId(null);
}
if (index === draggingIndex - 1) {
setFields(state => {
const fields = [...state];
[fields[draggingIndex], fields[draggingIndex - 1]] = [
fields[draggingIndex - 1],
fields[draggingIndex],
];
return fields;
});
} else {
setFields(state => {
const _fields = [...state];
const draggingFiled = _fields.splice(draggingIndex, 1)[0];
const index = _fields.findIndex(item => item.id === id);
if (index + 1 < _fields.length) {
_fields.splice(index + 1, 0, draggingFiled);
} else {
_fields.push(draggingFiled);
}
return _fields;
});
}
setDraggingId(null);
};
const unShiftFields = () => {
const draggingIndex = fields.findIndex(item => item.id === draggingId);
setFields(state => {
const _fields = [...state];
const field = _fields.splice(draggingIndex, 1);
_fields.unshift(...field);
return _fields;
});
setDraggingId(null);
setDroppingId(null);
};
return (
<Drawer
width={620}
title={t('Edit Table')}
visible={!!editingTable}
okText={t('Commit')}
cancelText={t('Cancel')}
autoFocus={false}
onOk={() => form.submit()}
onCancel={() => setEditingTable(false)}
escToExit={!props.formChange}
maskClosable={!props.formChange}
afterClose={() => {
props.onFormChange(false);
form.resetFields();
}}
>
<Form
onSubmit={save}
form={form}
labelAlign="left"
requiredSymbol={false}
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
onValuesChange={(changedValues, allValues) => {
if (!props.formChange) props.onFormChange(true);
}}
scrollToFirstError
>
<TableBaseForm />
{fields.map((field, index) => (
<TableFormItem
field={field}
key={field.id}
index={index}
fieldList={fields}
addItem={addItem}
removeItem={removeItem}
setFields={setFields}
onDragStart={onDragStart}
onDrop={onDrop}
draggingIndex={draggingIndex}
draggingId={draggingId}
droppingId={droppingId}
setDroppingId={setDroppingId}
setDraggingId={setDraggingId}
/>
))}
{fields.length === 0 && (
<Button
onClick={() => addItem(0)}
type="outline"
status="success"
size="small"
long
>
+ {t('Add field')}
</Button>
)}
</Form>
</Drawer>
);
}
import { useState, useEffect } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Button, Input, Menu, Tooltip } from '@arco-design/web-react';
import {
IconToLeft,
IconToRight,
IconSortAscending,
IconSortDescending,
IconFilter,
} from '@arco-design/web-react/icon';
import graphState from '../hooks/use-graph-state';
import { useTranslation } from 'react-i18next';
export default function TableNav({ onTableSelected, tableSelectedId, setTableSelectId }) {
const [collapsed, setCollapsed] = useState(false);
const [tableList, setTableList] = useState([]);
const [order, setOrder] = useState(1);
const [filterValue, setFilterValue] = useState('');
const [showFilter, setShowFilter] = useState(false);
const { tableList: tables } = graphState.useContainer();
const { t } = useTranslation('tablesNav');
useEffect(() => {
setTableList(tables);
return () => setTableList([]);
}, [tables]);
const handlerSort = () => {
if (!Array.isArray(tables) && !tables.length) return;
const newList = tables.sort((a, b) => {
const nameA = a.name.toUpperCase(); // ignore upper and lowercase
const nameB = b.name.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return order;
}
if (nameA > nameB) {
return -order;
}
// names must be equal
return 0;
});
setTableList(newList);
setOrder(val => -val);
};
const handlerFilter = e => {
const { value } = e.target;
if (!value) {
setTableList(tables);
return;
}
const newList = tableList.filter(
item => item.name.toUpperCase().indexOf(value.toUpperCase()) > -1
);
setTableList(newList);
setFilterValue(value);
};
useHotkeys('ctrl+., meta+.', () => setCollapsed(val => !val), { preventDefault: true }, [
tables,
]);
return (
<div className="left-table-nav">
<div className="table-nav-title">
<div>
{t(' Tables')}
<span className="table-nav-count">({tableList.length})</span>
</div>
<div>
<Button
size="mini"
icon={!collapsed ? <IconToLeft /> : <IconToRight />}
onClick={() => setCollapsed(val => !val)}
/>
<Button
size="mini"
icon={order === 1 ? <IconSortAscending /> : <IconSortDescending />}
onClick={() => handlerSort()}
/>
<Button
size="mini"
className={filterValue ? 'table-filter' : ''}
icon={<IconFilter />}
onClick={() => setShowFilter(val => !val)}
/>
</div>
</div>
{!collapsed && tableList.length ? (
<div className="table-nav-content">
{showFilter && (
<div className="table-nav-search">
<Input
style={{ width: 180 }}
allowClear
defaultValue={filterValue}
placeholder={t('input table name')}
onPressEnter={e => handlerFilter(e)}
onClear={() => {
setTableList(tables);
setFilterValue('');
}}
// onChange={val => setFilterValue(val)}
/>
</div>
)}
<Menu
className="table-nav-menu"
autoScrollIntoView
selectedKeys={[tableSelectedId]}
>
{tableList.map(table => (
<Menu.Item
key={table.id}
onClick={() => onTableSelected(table)}
onMouseOver={() => setTableSelectId(table.id)}
>
<Tooltip position="right" content={table.note || ''}>
<div>
{table.name}
<span className="table-nav-count">
({table.fields.length})
</span>
</div>
</Tooltip>
</Menu.Item>
))}
</Menu>
</div>
) : null}
</div>
);
}
/* Creating a database called graphDB and creating a table called graphs. */
import { Notification } from '@arco-design/web-react';
import { diffJson } from 'diff';
import i18n from 'i18next';
import { backendApi } from '@/client/api';
import { get, omit } from 'lodash';
function getViewGraphs(v) {
return {
...get(v, 'graph', {}),
...omit(v, 'graph'),
};
}
export const getAllGraphs = async () => {
const { data } = await backendApi.get('/schema/all');
const graphs = data.map(v => {
return getViewGraphs(v);
});
return graphs;
};
export const getGraph = async id => {
const { data } = await backendApi.get(`/schema/${id}`);
return getViewGraphs(data);
};
export const saveGraph = async ({ id, name, tableDict, linkDict, box }) => {
const { t } = i18n;
try {
const data = await getGraph(id);
const logJson = {
tableDict: data.tableDict,
linkDict: data.linkDict,
name: data.name,
};
if (diffJson({ tableDict, linkDict, name }, logJson).length > 1) {
await backendApi.put(`/schema/${id}`, {
name: name,
graph: {
tableDict,
linkDict,
box,
name,
},
});
}
Notification.success({
title: t('Save success'),
});
} catch (e) {
Notification.error({
title: t('Save failed'),
});
}
};
export const delGraph = async id => {
return await backendApi.delete(`/schema/${id}`);
};
export const addGraph = async (graph = {}, id = null) => {
const { data } = await backendApi.post('/schema/create', {
name: graph.name,
graph: {
...graph,
box: {
x: 0,
y: 0,
w: global.innerWidth,
h: global.innerHeight,
clientW: global.innerWidth,
clientH: global.innerHeight,
},
},
});
return getViewGraphs(data).id;
};
export const getLogs = async id => {
const { data } = await backendApi.get(`/schema/getLogs/${id}`);
return data.map(v => getViewGraphs(v));
};
export const delLogs = id => backendApi.delete(`/schema/getLogs/${id}`);
import { dbAdaptor } from './settings';
const dbc = {
indexed: require('./adaptor/indexed'),
}[dbAdaptor];
export const getAllGraphs = async () => await dbc.getAllGraphs();
export const getGraph = async id => await dbc.getGraph(id);
export const saveGraph = async args => await dbc.saveGraph(args);
export const delGraph = async id => await dbc.delGraph(id);
export const addGraph = async (graph = {}, id = null) => await dbc.addGraph(graph, id);
export const getLogs = async id => await dbc.getLogs(id);
export const delLogs = async id => await dbc.delLogs(id);
{
"id": "24c8146d-be20-488e-bb4b-e154549fee6b",
"tableDict": {
"fa28ec51-8edd-4add-8bc0-addf7018965a": {
"id": "fa28ec51-8edd-4add-8bc0-addf7018965a",
"name": "users",
"x": 109.5,
"y": 213,
"fields": [
{
"id": "25bfd0fc-72ae-49eb-9f48-d27e99161bac",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "22ed36dc-dadb-4ba8-b2b3-baa6f610293e",
"name": "name",
"type": "VARCHAR",
"note": "",
"dbdefault": "",
"unique": "on"
},
{
"id": "14b78710-0ef2-4f94-99b2-2f66be9041cf",
"name": "password",
"type": "VARCHAR",
"note": "",
"dbdefault": "",
"not_null": "on"
},
{
"id": "a1f596e0-e6fb-425c-8829-e79691e98b79",
"name": "sex",
"type": "VARCHAR",
"note": "",
"dbdefault": ""
},
{
"id": "3a8399fd-bbd3-4051-96dd-cff48129a59c",
"name": "email",
"type": "VARCHAR",
"note": "",
"dbdefault": ""
},
{
"id": "3c851c0f-b753-4e23-802b-366759c26176",
"name": "phone",
"type": "VARCHAR",
"note": "",
"dbdefault": ""
},
{
"id": "c9dd128a-ecf2-4b98-9a58-23273a2c8d0d",
"name": "avatar",
"type": "VARCHAR",
"note": "",
"dbdefault": ""
},
{
"id": "c8bc1de2-76ed-4361-8b08-a9981fe380a0",
"name": "last_login",
"type": "TIMESTAMP",
"note": "",
"dbdefault": ""
}
]
},
"847f8590-fc40-4be3-8e74-4c5367e1cead": {
"id": "847f8590-fc40-4be3-8e74-4c5367e1cead",
"name": "blogs",
"x": 685.5,
"y": 184,
"fields": [
{
"id": "cbb80b2d-b122-49d5-893f-00d2c781ff4a",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "f857df83-f6aa-4e21-8070-f2b98b348a58",
"name": "user_id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"not_null": "on"
},
{
"id": "3f947499-c9bd-4bcb-8793-c6c7e9492e73",
"name": "title",
"type": "VARCHAR",
"note": "",
"dbdefault": "",
"not_null": "on"
},
{
"id": "7d9ba3cb-d178-4909-970d-5115a90da758",
"name": "content",
"type": "TEXT",
"note": "",
"dbdefault": "",
"not_null": "on"
},
{
"id": "b26f9f06-a7a0-40e3-954a-cd780c273b28",
"name": "status",
"type": "VARCHAR",
"note": "",
"dbdefault": "",
"not_null": "on"
},
{
"id": "12107237-eb9d-4e4b-ae6c-e17409a985a5",
"name": "cover",
"type": "VARCHAR",
"note": "",
"dbdefault": ""
}
]
},
"9f4e211a-3d05-45c8-a594-078db3ec9129": {
"id": "9f4e211a-3d05-45c8-a594-078db3ec9129",
"name": "comments",
"x": 383.5,
"y": 76,
"fields": [
{
"id": "9b774537-fe5e-4482-a409-dbfd15a734c9",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "7962c6bb-6158-4a90-8520-afae6eeafa73",
"name": "blog_id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"not_null": "on"
},
{
"id": "0d9e438c-5f59-4e76-b7d9-ab9de97e1ece",
"name": "user_id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"not_null": "on"
},
{
"id": "b0506689-8493-417c-b137-ca81df4a5d18",
"name": "content",
"type": "VARCHAR",
"note": "",
"dbdefault": "",
"not_null": "on"
}
]
},
"3a66e5de-d527-42c1-b079-0d8a1b7d0bf0": {
"id": "3a66e5de-d527-42c1-b079-0d8a1b7d0bf0",
"name": "tags",
"x": 686.5,
"y": 450,
"fields": [
{
"id": "40ca7f00-08e9-4904-be0f-593da41a64f4",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "3553e2cc-a143-48d4-8245-8f70c266a237",
"name": "name",
"type": "VARCHAR",
"note": "",
"dbdefault": "",
"not_null": "on"
}
]
},
"0dfb6310-038e-42ff-ab3f-73a32f2d5be8": {
"id": "0dfb6310-038e-42ff-ab3f-73a32f2d5be8",
"name": "tag_blog",
"x": 987.5,
"y": 273,
"fields": [
{
"id": "6a43dc0f-d74c-4312-b9d8-9a50ce6339f3",
"name": "blog_id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on"
},
{
"id": "b2b962fd-74ca-48d1-9e11-00f349f3ee44",
"name": "tag_id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on"
}
]
}
},
"linkDict": {
"25b15d97-2802-42dc-a9bc-465c0cc2ce09": {
"id": "25b15d97-2802-42dc-a9bc-465c0cc2ce09",
"name": null,
"endpoints": [
{
"id": "fa28ec51-8edd-4add-8bc0-addf7018965a",
"fieldId": "25bfd0fc-72ae-49eb-9f48-d27e99161bac",
"relation": "1"
},
{
"id": "847f8590-fc40-4be3-8e74-4c5367e1cead",
"fieldId": "f857df83-f6aa-4e21-8070-f2b98b348a58",
"relation": "*"
}
]
},
"5440eece-efcf-4d83-9c04-53e131d0a059": {
"id": "5440eece-efcf-4d83-9c04-53e131d0a059",
"name": null,
"endpoints": [
{
"id": "847f8590-fc40-4be3-8e74-4c5367e1cead",
"fieldId": "cbb80b2d-b122-49d5-893f-00d2c781ff4a",
"relation": "1"
},
{
"id": "9f4e211a-3d05-45c8-a594-078db3ec9129",
"fieldId": "7962c6bb-6158-4a90-8520-afae6eeafa73",
"relation": "*"
}
]
},
"cc05e14d-46dc-4cb8-ac65-c2455ba0d491": {
"id": "cc05e14d-46dc-4cb8-ac65-c2455ba0d491",
"name": null,
"endpoints": [
{
"id": "fa28ec51-8edd-4add-8bc0-addf7018965a",
"fieldId": "25bfd0fc-72ae-49eb-9f48-d27e99161bac",
"relation": "1"
},
{
"id": "9f4e211a-3d05-45c8-a594-078db3ec9129",
"fieldId": "0d9e438c-5f59-4e76-b7d9-ab9de97e1ece",
"relation": "*"
}
]
},
"dd3d07b0-f2b0-436f-a35f-34ac7c6ae23c": {
"id": "dd3d07b0-f2b0-436f-a35f-34ac7c6ae23c",
"name": null,
"endpoints": [
{
"id": "847f8590-fc40-4be3-8e74-4c5367e1cead",
"fieldId": "cbb80b2d-b122-49d5-893f-00d2c781ff4a",
"relation": "1"
},
{
"id": "0dfb6310-038e-42ff-ab3f-73a32f2d5be8",
"fieldId": "6a43dc0f-d74c-4312-b9d8-9a50ce6339f3",
"relation": "*"
}
]
},
"bef8a4de-d4bc-4fe5-a9c9-817e36d223ed": {
"id": "bef8a4de-d4bc-4fe5-a9c9-817e36d223ed",
"name": null,
"endpoints": [
{
"id": "3a66e5de-d527-42c1-b079-0d8a1b7d0bf0",
"fieldId": "40ca7f00-08e9-4904-be0f-593da41a64f4",
"relation": "1"
},
{
"id": "0dfb6310-038e-42ff-ab3f-73a32f2d5be8",
"fieldId": "b2b962fd-74ca-48d1-9e11-00f349f3ee44",
"relation": "*"
}
]
}
},
"box": {
"x": -72.61778961592574,
"y": -35.942417753627836,
"w": 1372.4764397905756,
"h": 756,
"clientW": 1387,
"clientH": 764
},
"name": "Blog(example)",
"updatedAt": 1655451615323,
"createdAt": 1655451615323
}
{
"id": "4f8f68d1-7cf0-4272-8322-48d094462d52",
"tableDict": {
"cbe50657-f14b-4dde-986d-e9f7f490148b": {
"id": "cbe50657-f14b-4dde-986d-e9f7f490148b",
"name": "products",
"x": 314.211883544922,
"y": 164.81567382812523,
"fields": [
{
"id": "bf7d0dcc-c74c-4c48-aa6c-cdddd180b50b",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "97ee07cd-b492-477a-b242-d52069b3126b",
"name": "name",
"type": "VARCHAR",
"note": "",
"dbdefault": "",
"not_null": "on"
},
{
"id": "fbf4c7a1-a1d2-448a-8f8c-817ca13a6e0f",
"name": "supplier_id",
"type": "INTEGER",
"note": "",
"dbdefault": ""
},
{
"id": "03f73218-9d4d-4dc3-b8cc-7a2e27110556",
"name": "quantity_per_unit",
"type": "VARCHAR(20)",
"note": "",
"dbdefault": ""
},
{
"id": "e7e15ce8-7553-4e56-acd5-acf1f7bb0cb4",
"name": "unit_price",
"type": "BIGINT",
"note": "",
"dbdefault": ""
},
{
"id": "2624b8c7-cde7-4d82-b7d2-b1fe36f351e1",
"name": "units_in_stock",
"type": "SMALLINT",
"note": "",
"dbdefault": ""
},
{
"id": "ccaa89b9-2348-4f8d-80a2-34b58b11a26f",
"name": "units_on_order",
"type": "SMALLINT",
"note": "",
"dbdefault": ""
},
{
"id": "96b5edae-2619-4bd6-b7c8-68bcbef918d4",
"name": "recorder_level",
"type": "SMALLINT",
"note": "",
"dbdefault": ""
},
{
"id": "44da19c2-d662-4a02-81e5-256abd394c28",
"name": "discontinued",
"type": "BOOLEAN",
"note": "",
"dbdefault": ""
}
]
},
"dda1cbc2-bd16-4017-90e1-9f12f2ded1fd": {
"id": "dda1cbc2-bd16-4017-90e1-9f12f2ded1fd",
"name": "order_details",
"x": 598.5805053710939,
"y": 165.18432617187523,
"fields": [
{
"id": "1d63a5d0-49cc-41f8-a12c-34eb187ad586",
"name": "order_id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on"
},
{
"id": "d59d00fb-32cf-4065-9b4c-42c6bf134f56",
"name": "product_id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on"
},
{
"id": "e37e9993-3f39-4904-8dd4-5199a29b17c1",
"name": "unit_price",
"type": "BIGINT",
"note": "",
"dbdefault": ""
},
{
"id": "b426fe3f-f1f0-4901-89cb-db3d44956113",
"name": "quantity",
"type": "SMALLINT",
"note": "",
"dbdefault": ""
},
{
"id": "9d45a81e-ca5b-4123-8125-f8cc131cfdd8",
"name": "discount",
"type": "FLOAT",
"note": "",
"dbdefault": ""
}
]
},
"2f697744-feb5-4ca9-8ba7-1657992dbead": {
"id": "2f697744-feb5-4ca9-8ba7-1657992dbead",
"name": "suppliers",
"x": 20.94491577148449,
"y": 163.81567382812523,
"fields": [
{
"id": "946bc166-8025-435d-8012-80a84111cee1",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "324f123d-5f9e-414f-998a-fdbd194dc8a6",
"name": "company_name",
"type": "VARCHAR(40)",
"note": "",
"dbdefault": ""
},
{
"id": "77d4d7e2-ee12-46b8-803e-bbf3f39f454e",
"name": "contact_name",
"type": "VARCHAR(30)",
"note": "",
"dbdefault": ""
},
{
"id": "4e48454a-16ed-4bfa-b77f-12610730521f",
"name": "contact_title",
"type": "VARCHAR(30)",
"note": "",
"dbdefault": ""
},
{
"id": "b3e46869-c2d2-46a0-9cbd-915c947499b1",
"name": "address",
"type": "VARCHAR(60)",
"note": "",
"dbdefault": ""
},
{
"id": "4824a732-3ec3-4c9e-9030-500b1d7e1c13",
"name": "city",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "e51093a8-57ab-40e3-8515-15fc0b7504ef",
"name": "region",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "6c804951-483f-4cb7-aeaa-d9bb35a008ab",
"name": "postal_code",
"type": "VARCHAR(10)",
"note": "",
"dbdefault": ""
},
{
"id": "b33f57c2-6a14-4453-a0f5-6f522d539d9a",
"name": "country",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "a598c630-4ed7-42c0-92e9-8b0231d64047",
"name": "phone",
"type": "VARCHAR(24)",
"note": "",
"dbdefault": ""
},
{
"id": "5d0d3a15-57d1-4a17-be6d-4d92f5a243d7",
"name": "fax",
"type": "VARCHAR(24)",
"note": "",
"dbdefault": ""
},
{
"id": "fb3291b3-326b-4c1b-8cbc-7b787393bda0",
"name": "homepage",
"type": "TEXT",
"note": "",
"dbdefault": ""
}
]
},
"657f846f-f95d-4a76-915d-a4c3ee9328c8": {
"id": "657f846f-f95d-4a76-915d-a4c3ee9328c8",
"name": "orders",
"x": 903.7139892578126,
"y": 165.8156890869143,
"fields": [
{
"id": "c254e2aa-e28e-4c18-802e-fc52c3507003",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "a6fdfdf6-3e66-45d4-9bdd-ed3af957d11c",
"name": "customer_id",
"type": "INTEGER",
"note": "",
"dbdefault": ""
},
{
"id": "3c85993c-7fde-442c-ad1a-e51310234946",
"name": "employee_id",
"type": "INTEGER",
"note": "",
"dbdefault": ""
},
{
"id": "63799088-5bd5-45b2-b956-d57f1c32e301",
"name": "order_date",
"type": "DATE",
"note": "",
"dbdefault": ""
},
{
"id": "b2a6b844-8f13-4245-9a55-1e576f369dd0",
"name": "required_date",
"type": "DATE",
"note": "",
"dbdefault": ""
},
{
"id": "781a42d6-34ba-485d-b670-76f5a88c099d",
"name": "shipped_date",
"type": "DATE",
"note": "",
"dbdefault": ""
},
{
"id": "610c8b04-aa80-4f8b-a0ec-6d45ba073742",
"name": "ship_via",
"type": "INTEGER",
"note": "",
"dbdefault": ""
},
{
"id": "e3af8fc9-d29f-42ca-86d6-cb5a736b4efb",
"name": "freight",
"type": "BIGINT",
"note": "",
"dbdefault": ""
},
{
"id": "775da150-68cc-4bcf-acb8-c63a4d301384",
"name": "ship_name",
"type": "VARCHAR(40)",
"note": "",
"dbdefault": ""
},
{
"id": "4255c20f-8abc-4807-843e-79b39c2ebf93",
"name": "ship_address",
"type": "VARCHAR(60)",
"note": "",
"dbdefault": ""
},
{
"id": "abeed9f3-2a8e-4edb-a93e-84e9d6f0e3d3",
"name": "ship_city",
"type": "INTEGER",
"note": "",
"dbdefault": ""
},
{
"id": "c02c2276-50e8-43fe-a172-19ee2d97ffeb",
"name": "ship_region",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "ea06fca8-7cfc-47a1-8e39-e72d3661cedb",
"name": "ship_postal_code",
"type": "VARCHAR(10)",
"note": "",
"dbdefault": ""
},
{
"id": "5a17396d-0842-46bb-904b-3eab7f69d73c",
"name": "ship_country",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
}
]
},
"06b7531d-ffd4-4a5a-b06c-2b0c07ed0d64": {
"id": "06b7531d-ffd4-4a5a-b06c-2b0c07ed0d64",
"name": "employees",
"x": 602.0635375976562,
"y": 415.27334594726585,
"fields": [
{
"id": "97790223-20e1-4fc4-826c-533ec186ce9b",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "56cf5fae-6b9f-4297-8171-17b80b14ecca",
"name": "last_name",
"type": "VARCHAR(20)",
"note": "",
"dbdefault": ""
},
{
"id": "0a9ccafd-f889-455e-adce-84e31edcc195",
"name": "first_name",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "4a5f3af8-ef00-4aa4-baf8-18be40b1aa4b",
"name": "title",
"type": "VARCHAR(30)",
"note": "",
"dbdefault": ""
},
{
"id": "20b09cd5-cbb6-495a-bfb1-73c9db721eb3",
"name": "title_of_courtesy",
"type": "VARCHAR(25)",
"note": "",
"dbdefault": ""
},
{
"id": "47cfa892-549b-41d9-a6d1-d0a6f5234087",
"name": "birth_date",
"type": "DATE",
"note": "",
"dbdefault": ""
},
{
"id": "ac069666-da54-4405-9b0c-370193c0307a",
"name": "hire_date",
"type": "DATE",
"note": "",
"dbdefault": ""
},
{
"id": "6050e0a0-288e-4e73-93a4-601dd97f2a76",
"name": "address",
"type": "VARCHAR(60)",
"note": "",
"dbdefault": ""
},
{
"id": "7731938b-42a3-4fc0-9a9d-1ef1a92242dc",
"name": "city",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "735c501f-e6e5-4a66-8507-f47e257207ba",
"name": "region",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "5f1f586d-93f5-423d-9333-5b139072e82c",
"name": "postal_code",
"type": "VARCHAR(10)",
"note": "",
"dbdefault": ""
},
{
"id": "45949ad7-f0aa-413b-9aa8-ebcb4e20faae",
"name": "country",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "c7d970e3-a93b-41b3-9f0e-1084405c6692",
"name": "home_phone",
"type": "VARCHAR(24)",
"note": "",
"dbdefault": ""
},
{
"id": "b3526b5f-ec03-4e30-b05a-d5247227bbe7",
"name": "extension",
"type": "VARCHAR(4)",
"note": "",
"dbdefault": ""
},
{
"id": "4159e511-23c2-4848-ad9d-4777129e0aee",
"name": "photo",
"type": "TEXT",
"note": "",
"dbdefault": ""
},
{
"id": "d75141e8-2d20-4b1c-974b-6e766b1d18f9",
"name": "notes",
"type": "TEXT",
"note": "",
"dbdefault": ""
},
{
"id": "38a1fb55-f55b-4d9c-9216-4afece3c4691",
"name": "reports_to",
"type": "INTEGER",
"note": "",
"dbdefault": ""
}
]
},
"820e6ea5-c41b-441c-b9b6-87b9b707f6bc": {
"id": "820e6ea5-c41b-441c-b9b6-87b9b707f6bc",
"name": "customers",
"x": 1210.930071726376,
"y": 164.4661187640694,
"fields": [
{
"id": "83cea2b0-703c-4842-928c-037e4962091f",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "a5c3c1f5-79a4-4d03-97da-4c264c90b8cd",
"name": "company_name",
"type": "VARCHAR(40)",
"note": "",
"dbdefault": ""
},
{
"id": "7dbee685-8260-41aa-94ac-744f91ac29eb",
"name": "contact_name",
"type": "VARCHAR(30)",
"note": "",
"dbdefault": ""
},
{
"id": "3cb85b5a-bd3b-44be-98d2-dd751574b1be",
"name": "contact_title",
"type": "VARCHAR(30)",
"note": "",
"dbdefault": ""
},
{
"id": "2df85f95-b54a-4471-a0d8-a4d9dd34f2ed",
"name": "address",
"type": "VARCHAR(60)",
"note": "",
"dbdefault": ""
},
{
"id": "94ab4a23-61ef-41b1-8324-0b96eb71a424",
"name": "city",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "4fbc7300-d4ba-4e1a-b5c3-107b44b9e336",
"name": "region",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "af41ea00-e6a7-43a7-8212-2cc49ea846fe",
"name": "postal_code",
"type": "VARCHAR(10)",
"note": "",
"dbdefault": ""
},
{
"id": "d49fc11b-7a79-4ff9-af47-1fdf6c047f5f",
"name": "country",
"type": "VARCHAR(15)",
"note": "",
"dbdefault": ""
},
{
"id": "a5897c12-d581-4cda-b8f4-b886fdb15f7e",
"name": "phone",
"type": "VARCHAR(24)",
"note": "",
"dbdefault": ""
},
{
"id": "0f03d726-b1ee-47ff-9774-f7e088995650",
"name": "fax",
"type": "VARCHAR(24)",
"note": "",
"dbdefault": ""
}
]
},
"18a6b99f-3364-46c3-92cf-084ab55b99d0": {
"id": "18a6b99f-3364-46c3-92cf-084ab55b99d0",
"name": "shippers",
"x": 1210.7965402529248,
"y": 600.1631438691738,
"fields": [
{
"id": "18a90730-d730-4f62-8089-66b77dd45537",
"name": "id",
"type": "INTEGER",
"note": "",
"dbdefault": "",
"pk": "on",
"increment": "on"
},
{
"id": "1b4f4ef8-9895-4867-8234-375c6fa0dffc",
"name": "company_name",
"type": "VARCHAR(40)",
"note": "",
"dbdefault": ""
},
{
"id": "0191652d-bc4c-4d59-8498-123f26a4b728",
"name": "phone",
"type": "VARCHAR(24)",
"note": "",
"dbdefault": ""
}
]
}
},
"linkDict": {
"3d2b8333-2a81-400f-8bb2-392d851c4d11": {
"id": "3d2b8333-2a81-400f-8bb2-392d851c4d11",
"name": null,
"endpoints": [
{
"id": "2f697744-feb5-4ca9-8ba7-1657992dbead",
"fieldId": "946bc166-8025-435d-8012-80a84111cee1",
"relation": "1"
},
{
"id": "cbe50657-f14b-4dde-986d-e9f7f490148b",
"fieldId": "fbf4c7a1-a1d2-448a-8f8c-817ca13a6e0f",
"relation": "*"
}
]
},
"f3e5017a-1456-4a0c-8ebe-93d1cde71a9b": {
"id": "f3e5017a-1456-4a0c-8ebe-93d1cde71a9b",
"name": null,
"endpoints": [
{
"id": "cbe50657-f14b-4dde-986d-e9f7f490148b",
"fieldId": "bf7d0dcc-c74c-4c48-aa6c-cdddd180b50b",
"relation": "1"
},
{
"id": "dda1cbc2-bd16-4017-90e1-9f12f2ded1fd",
"fieldId": "d59d00fb-32cf-4065-9b4c-42c6bf134f56",
"relation": "*"
}
]
},
"2c4e4840-5a08-4535-8f03-f41ea2e085e9": {
"id": "2c4e4840-5a08-4535-8f03-f41ea2e085e9",
"name": null,
"endpoints": [
{
"id": "657f846f-f95d-4a76-915d-a4c3ee9328c8",
"fieldId": "c254e2aa-e28e-4c18-802e-fc52c3507003",
"relation": "1"
},
{
"id": "dda1cbc2-bd16-4017-90e1-9f12f2ded1fd",
"fieldId": "1d63a5d0-49cc-41f8-a12c-34eb187ad586",
"relation": "*"
}
]
},
"88fa58b9-ef51-4f87-8076-c8b8c5646ec8": {
"id": "88fa58b9-ef51-4f87-8076-c8b8c5646ec8",
"name": null,
"endpoints": [
{
"id": "06b7531d-ffd4-4a5a-b06c-2b0c07ed0d64",
"fieldId": "97790223-20e1-4fc4-826c-533ec186ce9b",
"relation": "1"
},
{
"id": "657f846f-f95d-4a76-915d-a4c3ee9328c8",
"fieldId": "3c85993c-7fde-442c-ad1a-e51310234946",
"relation": "*"
}
]
},
"891e3ebc-ca75-4346-91cd-f127b1953b11": {
"id": "891e3ebc-ca75-4346-91cd-f127b1953b11",
"name": null,
"endpoints": [
{
"id": "820e6ea5-c41b-441c-b9b6-87b9b707f6bc",
"fieldId": "83cea2b0-703c-4842-928c-037e4962091f",
"relation": "1"
},
{
"id": "657f846f-f95d-4a76-915d-a4c3ee9328c8",
"fieldId": "a6fdfdf6-3e66-45d4-9bdd-ed3af957d11c",
"relation": "*"
}
]
},
"a80bf0d0-869e-460c-917d-50a183494f80": {
"id": "a80bf0d0-869e-460c-917d-50a183494f80",
"name": null,
"endpoints": [
{
"id": "18a6b99f-3364-46c3-92cf-084ab55b99d0",
"fieldId": "18a90730-d730-4f62-8089-66b77dd45537",
"relation": "1"
},
{
"id": "657f846f-f95d-4a76-915d-a4c3ee9328c8",
"fieldId": "610c8b04-aa80-4f8b-a0ec-6d45ba073742",
"relation": "*"
}
]
}
},
"box": {
"w": 2082.71186440678,
"h": 1024.0000000000005,
"x": -20.311441805601135,
"y": 33.800845370132436,
"clientH": 944,
"clientW": 1920
},
"name": "NorthWind Traders(example)",
"updatedAt": 1654003144916,
"createdAt": 1654003144916
}
{
"id": "b9028252-8734-43f4-89cd-39849286d8ec",
"tableDict": {
"f6c10829-2bf2-4b0b-8fd1-e7ac4e6e410b": {
"id": "f6c10829-2bf2-4b0b-8fd1-e7ac4e6e410b",
"name": "capsule",
"x": 20.31690216064453,
"y": 0.8118114471435547,
"fields": [
{
"id": "e7543bc5-c4a3-49b0-af24-16acc1d879ca",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
},
{
"id": "1054bed8-41fd-4b96-8a3e-4839e86dc6ca",
"name": "serial",
"not_null": true,
"type": "VARCHAR"
},
{
"id": "06d79cb1-deb2-4ed0-9312-de4b9c559082",
"name": "status",
"type": "VARCHAR"
},
{
"id": "059a75d3-86de-470c-b3f1-e7c063c7bf68",
"name": "dragon",
"not_null": true,
"type": "UUID"
},
{
"id": "ad738fe6-d159-4a79-9cf3-1cb7a5761192",
"name": "reuse_count",
"not_null": true,
"type": "INT"
},
{
"id": "01eac2f3-b64c-47ab-bc73-77968ed4a105",
"name": "water_landings",
"not_null": true,
"type": "INT"
},
{
"id": "d6ecb02b-428e-4570-a6c3-bcf76d1d518a",
"name": "land_landings",
"not_null": true,
"type": "INT"
},
{
"id": "dba167c9-bcf2-4f91-9777-61988a2af61d",
"name": "last_update",
"type": "VARCHAR"
}
]
},
"3457494b-06b0-42a9-9bde-8f1c0681b142": {
"id": "3457494b-06b0-42a9-9bde-8f1c0681b142",
"name": "core",
"x": -1.662811279296875,
"y": 339.60548400878906,
"fields": [
{
"id": "faf303d2-7480-410d-9fd3-0c8ad05d26be",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
},
{
"id": "fd9d50f0-9437-4291-bfdc-b8766004b1b2",
"name": "serial",
"not_null": true,
"type": "VARCHAR"
},
{
"id": "86b88fe5-2cd6-4712-9e6b-daf0982be1f3",
"name": "block",
"type": "INT"
},
{
"id": "26b827df-a08d-4465-bbdc-aae489578356",
"name": "status",
"type": "VARCHAR"
},
{
"id": "daf48a64-2e07-4bd5-8b88-05b2c5ca64d1",
"name": "reuse_count",
"not_null": true,
"type": "INT"
},
{
"id": "e1e0a5a9-f5fa-4051-81fc-dd0f430923eb",
"name": "rtls_attempts",
"not_null": true,
"type": "INT"
},
{
"id": "2e7cbee6-9bca-4030-b6c5-df2bf383554f",
"name": "rtls_landings",
"not_null": true,
"type": "INT"
},
{
"id": "05df570f-4445-4f33-be13-473911199e9b",
"name": "asds_attempts",
"not_null": true,
"type": "INT"
},
{
"id": "05ed7ef0-1386-49b7-a668-9b322c580212",
"name": "asds_landings",
"not_null": true,
"type": "INT"
},
{
"id": "63ab7caf-0fcd-4968-916d-cfa332c8e4a3",
"name": "last_update",
"type": "VARCHAR"
}
]
},
"ed121af3-eb09-422b-b8fe-303dfb272e8b": {
"id": "ed121af3-eb09-422b-b8fe-303dfb272e8b",
"name": "crew",
"x": 69.77308654785156,
"y": -111.62432861328125,
"fields": [
{
"id": "b21becca-41ef-49f5-87a9-3dff7411c249",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
}
]
},
"87bfc39f-a135-46d7-bed7-bb5cc61b257c": {
"id": "87bfc39f-a135-46d7-bed7-bb5cc61b257c",
"name": "dragon",
"x": 312.1395263671875,
"y": 24.3756103515625,
"fields": [
{
"id": "7f3ce63d-de09-4772-b211-17fa130089f6",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
}
]
},
"2b62ff08-449c-4e1e-9927-5e4a10d801e5": {
"id": "2b62ff08-449c-4e1e-9927-5e4a10d801e5",
"name": "rocket",
"x": 316.95440673828125,
"y": 147.61802291870117,
"fields": [
{
"id": "6cf338a8-fb39-480d-aacf-542ec6832e54",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
}
]
},
"a7bdd7ef-eaf7-49ba-bff4-b879e7bfca53": {
"id": "a7bdd7ef-eaf7-49ba-bff4-b879e7bfca53",
"name": "landpad",
"x": 311.849609375,
"y": 284.5332221984863,
"fields": [
{
"id": "a5dbfcf3-1064-4211-88f8-4516c47c6e3b",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
},
{
"id": "580e2cb6-e542-4ab1-bd74-0bcbd3c61030",
"name": "name",
"type": "VARCHAR"
},
{
"id": "e56d6f5d-2f74-4cc3-bb1c-7e2dae8a969b",
"name": "full_name",
"type": "VARCHAR"
},
{
"id": "f68f5834-e82e-470f-b0c5-d238659dbe00",
"name": "status",
"type": "VARCHAR"
},
{
"id": "74c76436-6055-425f-9b4b-30eea90d4e7e",
"name": "type",
"type": "VARCHAR"
},
{
"id": "9ac9a320-612b-4022-8039-109e3a3e9c73",
"name": "locality",
"type": "VARCHAR"
},
{
"id": "56c37522-9629-486b-b263-7fe89f413f8b",
"name": "region",
"type": "VARCHAR"
},
{
"id": "6e500fba-342c-4ebe-9fa6-eed2064a9643",
"name": "latitude",
"type": "FLOAT"
},
{
"id": "9dfebee5-6e3f-4746-9f6c-2d39f089f881",
"name": "longitude",
"type": "FLOAT"
},
{
"id": "966c7cfd-cafa-42b8-8107-9e78dbfc538b",
"name": "attempted_landings",
"not_null": true,
"type": "INT"
},
{
"id": "990a6c8f-d785-4c49-b44c-d58a1586612b",
"name": "successful_landings",
"not_null": true,
"type": "INT"
},
{
"id": "2919f5fb-a182-4a16-9564-0e4ffbd460db",
"name": "details",
"type": "VARCHAR"
}
]
},
"66e6edb0-d45c-4f7e-b334-dec67b9186bb": {
"id": "66e6edb0-d45c-4f7e-b334-dec67b9186bb",
"name": "mission",
"x": 395.776611328125,
"y": -154.20775604248047,
"fields": [
{
"id": "80d8e79f-da24-4164-b58b-bea4b8ef9645",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
},
{
"id": "21800b1d-de72-43d7-b2fc-6e1fa7799e82",
"name": "name",
"type": "VARCHAR"
},
{
"id": "c2cee9bb-c273-4e0d-a2bd-1ca8f8cde380",
"name": "details",
"type": "VARCHAR"
}
]
},
"a2854635-4453-4b42-897f-b513c14c32b0": {
"id": "a2854635-4453-4b42-897f-b513c14c32b0",
"name": "launch",
"x": 647.3829956054688,
"y": -159.66927337646484,
"fields": [
{
"id": "5cec04c2-f21a-4a99-ac80-ba30c17e0107",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
},
{
"id": "0557e13d-f0fd-4aaa-941a-d3327a0252e5",
"name": "flight_number",
"type": "INT"
},
{
"id": "e3c4396a-11a5-4d25-bdf3-0944141260fa",
"name": "name",
"type": "VARCHAR"
},
{
"id": "8c667b61-ea7b-4fef-bc6f-67ea104c3b7e",
"name": "date_utc",
"type": "DATETIME"
},
{
"id": "b6953ab8-d56e-4dee-92d3-6341f084c032",
"name": "date_local",
"type": "DATETIME"
},
{
"id": "95c53246-c28a-4134-808d-c5d5d5f0d14f",
"name": "date_precision",
"type": "VARCHAR"
},
{
"id": "ff6da274-45c3-459b-a4b9-8f0648d3bdbe",
"name": "static_fire_date_utc",
"type": "DATETIME"
},
{
"id": "5ea64d4c-85ac-42d6-9308-5a4ac2ca0237",
"name": "tdb",
"type": "BOOLEAN"
},
{
"id": "64f54ab2-ab3a-4502-8ce5-e64d8f7c6199",
"name": "net",
"type": "BOOLEAN"
},
{
"id": "adcbb960-bbc4-45b5-8b9d-95eb67b30689",
"name": "window",
"type": "INT"
},
{
"id": "faa84872-e345-4450-90e9-dc958cad7b3a",
"name": "rocket",
"type": "UUID"
},
{
"id": "f8495651-bbba-46d9-b69d-c63b013a525e",
"name": "launch_success",
"type": "BOOLEAN"
},
{
"id": "7a5b4a8b-7bbf-4ac7-a471-1ba46c62f84b",
"name": "upcoming",
"type": "BOOLEAN"
},
{
"id": "e3b7e0a3-9d9a-41e3-a4b8-2c1d388e6a26",
"name": "details",
"type": "VARCHAR"
}
]
},
"08ffd841-3fc6-4d76-8efb-55ad368db8e8": {
"id": "08ffd841-3fc6-4d76-8efb-55ad368db8e8",
"name": "launchpad",
"x": 620.1926879882812,
"y": 348.6057548522949,
"fields": [
{
"id": "516f4687-bccf-4161-aeae-11b5cf839fa7",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
},
{
"id": "15232f24-1a6f-4c5d-81f9-9ccab138f877",
"name": "name",
"type": "VARCHAR"
},
{
"id": "d6ff6243-9a3a-4767-91b3-b7d06dd4bb7c",
"name": "full_name",
"type": "VARCHAR"
},
{
"id": "bdd7817a-a69b-49d1-8aba-7348d58f6d3e",
"name": "status",
"type": "VARCHAR"
},
{
"id": "6c90f6e4-8048-4aed-9868-ef9e90135c17",
"name": "locality",
"type": "VARCHAR"
},
{
"id": "fd96d590-2011-4449-b6cc-6fe33fe61265",
"name": "region",
"type": "VARCHAR"
},
{
"id": "23141120-a7ec-476e-947d-46754a0924b3",
"name": "latitude",
"type": "FLOAT"
},
{
"id": "c80f2290-c848-467e-8b70-c97ca9da4898",
"name": "longitude",
"type": "FLOAT"
},
{
"id": "7a4e9c30-52b8-400d-8409-80178f859d07",
"name": "attempted_launches",
"not_null": true,
"type": "INT"
},
{
"id": "b2e50fce-81d9-4112-b1ab-adcbabc6e8bf",
"name": "successful_launches",
"not_null": true,
"type": "INT"
}
]
},
"e6874542-0c07-488c-96cf-f1b4ea241995": {
"id": "e6874542-0c07-488c-96cf-f1b4ea241995",
"name": "payload",
"x": 904.2288818359375,
"y": -103.34548430144787,
"fields": [
{
"id": "4ba1fcfa-e38f-463d-b11e-50e9fae67ae7",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
},
{
"id": "fc563b1e-0453-48d5-86b2-586c9b2deb89",
"name": "name",
"type": "VARCHAR"
},
{
"id": "6b3dcb61-6cd2-42d3-86c5-ecf73dadd7d7",
"name": "type",
"type": "VARCHAR"
},
{
"id": "1a03eced-c9e3-48a4-9cc1-76556af79a8f",
"name": "reused",
"type": "BOOLEAN"
},
{
"id": "9b9dabc3-51ea-41fa-83f8-1bd573458f9b",
"name": "customers",
"type": "VARCHAR[]"
},
{
"id": "e617de1a-ff52-4923-969d-7a172a6d1213",
"name": "norad_ids",
"type": "INT[]"
},
{
"id": "5183d817-7487-4808-85b4-ec10c86e5657",
"name": "nationality",
"type": "VARCHAR"
},
{
"id": "1693ac27-8fa5-4bd3-8146-ab6d4d6b1e8d",
"name": "manufacturer",
"type": "VARCHAR"
},
{
"id": "39b4e126-da39-4fd5-ad15-317bf6fd0d3b",
"name": "mass_kg",
"type": "FLOAT"
},
{
"id": "6d563d31-d244-45b2-9845-7b193683d295",
"name": "mass_lbs",
"type": "FLOAT"
},
{
"id": "d0047098-820c-43c3-b808-9c411304012f",
"name": "orbit",
"type": "VARCHAR"
},
{
"id": "6ad8a1aa-e8e8-4d2a-a55c-33f31cda40aa",
"name": "reference_system",
"type": "VARCHAR"
},
{
"id": "01eef233-8983-43c4-a360-103f081baf0e",
"name": "regime",
"type": "VARCHAR"
},
{
"id": "3ef296bc-a279-445c-823b-1f363c5ec2ff",
"name": "longitude",
"type": "FLOAT"
},
{
"id": "19d510d3-55d3-4818-ac45-00e9363aba17",
"name": "semi_major_axis_km",
"type": "FLOAT"
},
{
"id": "e8c639e6-0f26-4c6c-8e73-6f97ffeb504d",
"name": "eccentricity",
"type": "FLOAT"
},
{
"id": "83e269fe-0668-4024-a129-4db7470bd8ea",
"name": "periapsis_km",
"type": "FLOAT"
},
{
"id": "6bca28cb-ee3b-453b-8e37-16e69740fe95",
"name": "apoapsis_km",
"type": "FLOAT"
},
{
"id": "37bd077d-4417-49fe-ab1b-4f089b048087",
"name": "inclination_deg",
"type": "FLOAT"
},
{
"id": "f5df0e8f-28c5-4142-bf08-cf55b43a598a",
"name": "period_min",
"type": "FLOAT"
},
{
"id": "163d2f5f-3944-45d2-80ad-a15bcda6b426",
"name": "lifespan_years",
"type": "INT"
},
{
"id": "e358876d-12e2-4a27-b196-1b00ca46719a",
"name": "epoch",
"type": "DATETIME"
},
{
"id": "5e66e3b2-5c3f-4d21-a013-40c13fc0afc8",
"name": "mean_motion",
"type": "FLOAT"
},
{
"id": "58eb8e36-032c-4bf1-9c21-5272d9494e3e",
"name": "raan",
"type": "FLOAT"
},
{
"id": "97ef4740-54e5-4361-852c-f9c19ec9b6cd",
"name": "arg_of_pericenter",
"type": "FLOAT"
},
{
"id": "57290dcf-a85c-4cc4-88f7-f96e1dd95de6",
"name": "mean_anomaly",
"type": "FLOAT"
},
{
"id": "a51ddda6-d871-41dc-ab6b-0188d437a5e8",
"name": "mass_returned_kg",
"type": "FLOAT"
},
{
"id": "ec714dd8-a83b-4eeb-9264-4770b33033bb",
"name": "mass_returned_lbs",
"type": "FLOAT"
},
{
"id": "e4d9743b-fa0c-4bd7-8a14-54827706c004",
"name": "flight_time_sec",
"type": "INT"
},
{
"id": "555249e4-6c43-4331-a685-4ce4958963ee",
"name": "manifest",
"type": "VARCHAR"
}
]
},
"dec9086b-8287-4a4a-b236-faf8a15c839c": {
"id": "dec9086b-8287-4a4a-b236-faf8a15c839c",
"name": "ship",
"x": 1169.684326171875,
"y": -36.95626640319824,
"fields": [
{
"id": "3a95243f-6bbf-4626-a7ef-063973b20c48",
"name": "id",
"pk": true,
"unique": true,
"type": "UUID"
},
{
"id": "653335fa-a597-4cbc-8c76-e3de259ba9b9",
"name": "name",
"type": "VARCHAR"
},
{
"id": "4a143478-d5b7-44f0-9bc2-9b415f1ca3f1",
"name": "model",
"type": "VARCHAR"
},
{
"id": "cf4c2a58-5922-45cf-a0e6-c206ab4635e3",
"name": "type",
"type": "VARCHAR"
},
{
"id": "8e89707b-7e4a-4904-b30e-ccf832dd2421",
"name": "roles",
"type": "VARCHAR[]"
},
{
"id": "17b5265c-5c48-4bf1-8dfc-98e53c7fd11b",
"name": "active",
"type": "BOOLEAN"
},
{
"id": "e118503e-799a-4597-a0c4-b244de81e81e",
"name": "imo",
"type": "INT"
},
{
"id": "3fb2c8ea-9a65-4d66-bd86-3a4f4147e78b",
"name": "mmsi",
"type": "INT"
},
{
"id": "80ae8e1b-e505-4883-87c3-a580f2f63d7c",
"name": "abs",
"type": "INT"
},
{
"id": "0b3b6464-9153-4c6a-a804-9616cb0aa366",
"name": "class",
"type": "INT"
},
{
"id": "ff9e04de-4d9e-42f2-a82b-fbb6b0aea534",
"name": "mass_kg",
"type": "FLOAT"
},
{
"id": "c9f1f213-9d17-49b1-9e07-f12a4fa95499",
"name": "mass_lbs",
"type": "FLOAT"
},
{
"id": "8fdb1cb0-2975-43fd-ac0d-ffd40adeebbc",
"name": "year_built",
"type": "INT"
},
{
"id": "2b55e213-e1a5-4b4d-b8d0-a53a57ac4810",
"name": "home_port",
"type": "VARCHAR"
},
{
"id": "7f24d343-51b3-4e8b-adbc-4c1f124171df",
"name": "status",
"type": "VARCHAR"
},
{
"id": "88913eeb-38f9-4526-bf7d-3720abc9b181",
"name": "speed_kn",
"type": "FLOAT"
},
{
"id": "c589819d-823f-4e57-b1a3-ff09edee805d",
"name": "course_deg",
"type": "FLOAT"
},
{
"id": "9fa5b58b-3173-41ce-903b-3ebb2d4d4c10",
"name": "latitude",
"type": "FLOAT"
},
{
"id": "6f1a22bf-70b3-4e2b-8179-948c8bca5fbd",
"name": "longitude",
"type": "FLOAT"
},
{
"id": "5ecdeec9-39f8-4ca8-b47a-dc334fd52512",
"name": "link",
"type": "VARCHAR"
},
{
"id": "d71e6612-eed7-45d5-adcb-dfad2be2f29c",
"name": "image",
"type": "VARCHAR"
}
]
}
},
"linkDict": {
"db44be04-da59-473f-8bcf-1c2590c4901e": {
"id": "db44be04-da59-473f-8bcf-1c2590c4901e",
"endpoints": [
{
"id": "87bfc39f-a135-46d7-bed7-bb5cc61b257c",
"relation": "1",
"fieldId": "7f3ce63d-de09-4772-b211-17fa130089f6"
},
{
"id": "f6c10829-2bf2-4b0b-8fd1-e7ac4e6e410b",
"relation": "*",
"fieldId": "059a75d3-86de-470c-b3f1-e7c063c7bf68"
}
]
},
"f413bb57-0775-4ad1-839d-1f1b88b0a9de": {
"id": "f413bb57-0775-4ad1-839d-1f1b88b0a9de",
"endpoints": [
{
"id": "2b62ff08-449c-4e1e-9927-5e4a10d801e5",
"relation": "1",
"fieldId": "6cf338a8-fb39-480d-aacf-542ec6832e54"
},
{
"id": "a2854635-4453-4b42-897f-b513c14c32b0",
"relation": "*",
"fieldId": "faa84872-e345-4450-90e9-dc958cad7b3a"
}
]
}
},
"box": {
"w": 2214.3661301781253,
"h": 1219.7373637030196,
"x": -397.8229480508105,
"y": -302.7157040934959,
"clientH": 764,
"clientW": 1387
},
"name": "SpaceX API(example)",
"updatedAt": 1655451359641,
"createdAt": 1655451359641
}
const fieldTypes = [
'INTEGER',
'SMALLINT',
'BIGINT',
'NUMERIC',
'FLOAT',
'DOUBLE',
'BOOLEAN',
'CHARACTER',
'VARCHAR',
'TEXT',
'DATE',
'TIME',
'TIMESTAMP',
'JSON',
'BLOB',
];
export default fieldTypes;
type dbml = string
export const GET_QUERY = (sql:dbml) =>`作为一个数据库模型业务分析专家,您需要根据当前数据库模型(DBML格式)生成对应的 MySQL 数据库可执行的 SQL 文本,并列出执行该 SQL 所需的变量及其解释,最后为此条查询命名。当前数据库模型为:
\`\`\`dbml
${sql}
\`\`\`
请您使用以下模版格式输出结果(注意模版中的标签和变量名称是必须存在的):
=======模版开始=======
根据您提供的数据库模型,已为您生成查询:
<sql> &=&sql&=& </sql>
执行所需变量:
判断解决当前问题的查询执行时是否需要变量。若条件成立,请使用:
<var>执行 SQL 所需变量名称</var>
<varDescription>变量解释</varDescription>
若不需要变量,则无需提供任何信息。
查询命名和描述:
<queryName> &=& 查询名称 &=& </queryName>
<queryDescription> &=& 查询描述 &=& </queryDescription>
=======模版结束=======
请您仅替换模版中的 &=& 和 &=& 之间的文字,变量命名请放在 $ 和 $ 之间,并且保持模版排版一致。例如:
根据您提供的数据库模型,已为您生成查询:
<sql>
SELECT $field$ FROM users where id=$id$;
INSERT INTO users (email, name) VALUES ($email$, $name$);
</sql>
执行所需变量:
<var> $field$ </var>: <varDescription> users 表中的某个字段。</varDescription>
查询命名和描述:
<queryName> 查用户和插入用户 </queryName>
<queryDescription> 先根据用户id查出用户,然后插入一个用户。 </queryDescription>
`
export const GET_SCHEMA_INFO = (sql:dbml) =>`作为一个数据模型业务分析专家,您需要根据当前数据模型(DBML格式)进行精确的分析,根据模型的提供的信息,分析该模型并输出简要描述。当前数据库模型为:
<dbml>
${sql}
</dbml>
`
export const ADD_TABLE = (sql:dbml)=>`作为一位资深的业务分析的专家,你需要结合当前数据库模型(DBML格式)并精确地分析我需要在当前数据库模型新增加的业务需求,向当前数据库模型添加(DBML格式)新表或者修改表字段之间的关联关系。当前数据库模型为:
<dbml>
${sql}
</dbml>
请您使用以下模版格式输出结果 (输出格式:XML,注意:XMl标签需要保留。dbml文本为新的模型)
=======模版开始=======
根据您提供的业务需求和数据库模型,已为您生成新的DBML数据库模型:
<dbml>
{{dbml文本}}
</dbml>
已经为你生成:<tableName>{{表名}}</tableName> <tableName>{{表名}}</tableName>
<field>{{表名}}:{{字段}}</field>:<description>{{表的字段描述}}</description>
=======模版结束=======
请您仅替换模版中的{{和}}之间的文字,并且保持模版排版一致。例如下面这个输出案例:
根据您提供的业务需求和数据库模型,已为您生成新的DBML数据库模型:
<dbml>Table ecommerce.merchants {
id int
country_code int
merchant_name varchar
"created at" varchar
admin_id int [ref: > U.id]
}
// If schema name is omitted, it will default to "public" schema.
Table users as U {
id int [pk, increment] // auto-increment
full_name varchar
}</dbml>
已经为你生成:<tableName>users</tableName>
<field>product_tags.id</field>:<description>主键</description>
<field>full_name</field>:<description>姓名</description>`
export const ADD_SCHEMA=()=>`你作为一个擅长业务分析的数据库模型建表专家,您需要分析我的业务需求并且输出 DBML格式 的数据库模型,添加一个或多个表和新增的表字段之间的关联关系。
请您使用以下模版格式输出结果 (输出格式:XML,注意:XMl标签需要保留)
=======模版开始=======
根据您提供的业务需求和数据库模型,已为您生成新的DBML数据库模型:
<modelName>该模型名称</modelName>
<dbml>{{dbml文本}}</dbml>
已经为你生成:<tableName>{{表名}}</tableName> <tableName>{{表名}}</tableName>
=======模版结束=======
请您仅替换模版中的{{和}}之间的文字,并且保持模版排版一致。例如输出:
根据您提供的业务需求和数据库模型,已为您生成新的DBML数据库模型:
<modelName>电子商务</modelName>
<dbml>Table ecommerce.merchants {
id int
country_code int
merchant_name varchar
"created at" varchar
admin_id int [ref: > U.id]
}
// If schema name is omitted, it will default to "public" schema.
Table users as U {
id int [pk, increment] // auto-increment
full_name varchar
created_at timestamp
country_code int
}</dbml>
已经为你生成:<tableName>merchants</tableName> <tableName>users</tableName>
`
\ No newline at end of file
export const CLEAR_ALL_TABLES_DATA = "重置表中所有数据,包括重置主键"
export const QUERY_ALL_DATA = "查询所有表中的数据"
export const LIST_ALL_TABLES = "查出所有的表"
export const CHINESE_DATA_POPULATOR = "分析模型表之间的关联关系,在数据库每个表中插入数量不等的生产环境标准的中文模拟数据"
\ No newline at end of file
export const themes = [
['#CB2E34', '#eb0aa4', '#CC961F', '#00b42a'],
['#CFB325', '#84B723', '#D26913', '#ff7d00'],
['#2971CF', '#B01BB6', '#0fc6c2', '#ffb400'],
];
export const tableWidth = 240;
export const tableMarginLeft = 48;
export const tableMarginTop = 48;
export const tableRowNumbers = 5;
export const titleHeight = 40;
export const commentHeight = 24;
export const fieldHeight = 32;
export const dbAdaptor = 'indexed';
import { nanoid } from 'nanoid';
import graphState from './use-graph-state';
import { saveGraph, getGraph } from '../data/db';
import {
tableWidth,
tableMarginLeft,
tableMarginTop,
tableRowNumbers,
fieldHeight,
titleHeight,
commentHeight,
} from '../data/settings';
const tableModel = () => {
const {
id,
tableList,
tableDict,
setTableDict,
linkDict,
setLinkDict,
box,
name,
setName,
setVersion,
setEditingTable,
setEditingField,
setAddingField,
saveCheck,
} = graphState.useContainer();
const updateGraph = async () => {
await saveGraph({ id, name, tableDict, linkDict, box });
setVersion('currentVersion');
};
const calcXY = (start, tables = tableList) => {
const index = start || Math.max(1, tables.length);
let x, y;
if (!tables.length) {
x = box.x + 196 + 72;
y = box.y + 72;
} else {
if (index < tableRowNumbers) {
const lastTable = tables[index - 1];
x = lastTable.x + tableWidth + tableMarginLeft;
y = lastTable.y;
} else {
const lastTable = tables[index - tableRowNumbers];
const { fields } = lastTable;
x = lastTable.x;
y =
lastTable.y +
fields.length * fieldHeight +
titleHeight +
commentHeight +
tableMarginTop;
}
}
return [x, y];
};
/**
* It creates a new table object and adds it to the table dictionary
*/
const addTable = () => {
const [x, y] = calcXY();
const id = nanoid();
const newTable = {
[id]: {
id,
name: `Table Name ${tableList.length + 1}`,
x,
y,
fields: [
{
id: nanoid(),
name: 'id',
type: 'INTEGER',
pk: true,
increment: true,
},
],
},
};
// setTableDict(state => ({ ...state, ...newTable }));
setEditingTable(newTable[id]);
};
/**
* It takes a table object, updates the tableDict state with the new table object, and then sets
* the editingTable state to null
*/
const updateTable = table => {
if (table) {
setTableDict(state => {
return {
...state,
[table.id]: {
...state[table.id],
...table,
},
};
});
}
setLinkDict(state => {
const newState = { ...state };
Object.keys(newState).forEach(key => {
if (
newState[key].endpoints.some(
endpoint =>
endpoint.id === table.id &&
!table.fields.some(field => field.id === endpoint.fieldId)
)
)
delete newState[key];
});
return newState;
});
setEditingTable(null);
setEditingField({});
};
/**
* It removes a table from the table dictionary, and removes any links that are connected to that table
*/
const removeTable = tableId => {
setTableDict(state => {
const newState = { ...state };
delete newState[tableId];
return newState;
});
setLinkDict(state => {
const newState = { ...state };
Object.keys(newState).forEach(key => {
if (newState[key].endpoints.find(endpoint => endpoint.id === tableId)) {
delete newState[key];
}
});
return newState;
});
setEditingTable(null);
};
const addField = (table, index) => {
table.fields.splice(index + 1, 0, {
id: nanoid(),
name: 'new item' + table.fields.length,
type: 'VARCHAR',
unique: false,
});
setTableDict(state => {
return {
...state,
[table.id]: {
...state[table.id],
...table,
},
};
});
setEditingField({ field: table.fields[index + 1], table });
setAddingField({ index: index + 1, table });
};
const removeField = (table, index) => {
const [filed] = table.fields.splice(index, 1);
setTableDict(state => {
return {
...state,
[table.id]: {
...state[table.id],
...table,
},
};
});
setLinkDict(state => {
const newState = { ...state };
Object.keys(newState).forEach(key => {
if (newState[key].endpoints.find(endpoint => endpoint.fieldId === filed.id)) {
delete newState[key];
}
});
return newState;
});
};
const applyVersion = async item => {
let graph;
if (item === 'currentVersion') {
graph = await getGraph(id);
graph.updatedAt = 'currentVersion';
} else {
graph = item;
}
setVersion(graph.updatedAt);
setTableDict(graph.tableDict);
setLinkDict(graph.linkDict);
setName(graph.name);
};
return {
updateGraph,
addTable,
updateTable,
removeTable,
addField,
removeField,
applyVersion,
calcXY,
};
};
export default tableModel;
import { useState, useEffect, useCallback, useMemo } from 'react';
import { createContainer } from 'unstated-next';
import { useRouter } from 'next/router';
import { Modal } from '@arco-design/web-react';
import { getGraph } from '../data/db';
import { useTranslation } from 'react-i18next';
/**
* It returns a state object that contains the graph data, and a set of functions to update the graph
* data
* @returns An object with the following properties:
*/
function useGraphState() {
const [init, setInit] = useState(false);
const [name, setName] = useState('Untitled graph');
const [theme, setTheme] = useState('');
const [version, setVersion] = useState('currentVersion');
// viewbox of svg
const [box, setBox] = useState({ x: 0, y: 0, w: 0, h: 0, clientW: 0, clientH: 0 });
const [tableDict, setTableDict] = useState({});
const [linkDict, setLinkDict] = useState({});
const [editingTable, setEditingTable] = useState();
const [editingField, setEditingField] = useState({});
const [addingField, setAddingField] = useState(null);
const router = useRouter();
const [queryId, setQueryId] = useState('');
const { id } = queryId ? { id: queryId } : router.query;
// const isGraphView = router.route === '/graphs/[id]';
const { t } = useTranslation('table');
const [currentGraph, setCurrentGraph] = useState({});
// const id = new URLSearchParams(global?.location?.search).get('id');
/* A callback function that is used to update the viewbox of the svg. */
const resizeHandler = useCallback(() => {
setBox(state => {
return {
x: state.x,
y: state.y,
w:
state.w && state.clientW
? state.w * (window.innerWidth / state.clientW)
: window.innerWidth,
h:
state.h && state.clientH
? state.h * (window.innerHeight / state.clientH)
: window.innerHeight,
clientW: window.innerWidth,
clientH: window.innerHeight,
};
});
}, []);
useEffect(() => {
window.addEventListener('resize', resizeHandler);
return () => {
window.removeEventListener('resize', resizeHandler);
};
}, []);
/**
* It takes a graph object and sets the state of the app to match the graph object
*/
const loadGraph = graph => {
if (!graph) return resizeHandler();
if (graph.tableDict) setTableDict(graph.tableDict);
if (graph.linkDict) setLinkDict(graph.linkDict);
setCurrentGraph(graph);
if (graph.box) {
const { x, y, w, h, clientH, clientW } = graph.box;
setBox({
x,
y,
w: w && clientW ? w * (window.innerWidth / clientW) : window.innerWidth,
h: h && clientH ? h * (window.innerHeight / clientH) : window.innerHeight,
clientW: window.innerWidth,
clientH: window.innerHeight,
});
}
if (graph.name) setName(graph.name);
};
const saveCheck = useCallback(
fx => {
const check = async () => {
if (id) {
const graph = await getGraph(id);
const storageGraph = JSON.parse(window.localStorage.getItem(id));
if (graph?.updatedAt < storageGraph?.updatedAt) {
Modal.confirm({
title: t('Unsaved changes'),
content: t('Do you want to save your changes before leaving?'),
cancelButtonProps: { status: 'danger' },
okText: t('Save'),
cancelText: t('Cancel'),
onOk: () => {
fx && fx(true);
},
onCancel: () => {
window.localStorage.removeItem(id);
fx && fx();
},
});
} else {
resizeHandler();
fx && fx();
}
}
};
check();
},
[id]
);
useEffect(() => {
if (!id) {
setTableDict({});
setLinkDict({});
setName('');
resizeHandler();
setCurrentGraph({});
return;
}
/**
* > If the graph is in the local storage, and the graph in the local storage is newer than the
* graph in the database, then ask the user if they want to load the graph from the local
* storage
*/
const initGraph = async () => {
setInit(true);
const graph = await getGraph(id);
loadGraph(graph);
const storageGraph = JSON.parse(window.localStorage.getItem(id));
if (graph?.updatedAt < storageGraph?.updatedAt) {
Modal.confirm({
title: t('Unsaved changes'),
content: t(
'You have some unsaved changes after last version, do you want to restore them? Once you press the no button, the unsaved changes will be cleaned immediately. You can’t undo this action.'
),
cancelButtonProps: { status: 'danger' },
okText: t('Yes, restore them'),
cancelText: t('No, ignore them'),
onOk: () => {
loadGraph(storageGraph);
},
onCancel: () => {
window.localStorage.removeItem(id);
},
});
} else {
resizeHandler();
}
};
initGraph();
}, [id]);
useEffect(() => {
if (init) setInit(false);
if (!id || init || !Object.keys(tableDict || {}).length) return;
window.localStorage.setItem(
id,
JSON.stringify({
id,
tableDict,
linkDict,
box,
name,
updatedAt: new Date().valueOf(),
})
);
}, [box, linkDict, tableDict, name]);
useEffect(() => {
const t = theme || window.localStorage.getItem('theme') || 'light';
t === 'dark'
? document.body.setAttribute('arco-theme', 'dark')
: document.body.removeAttribute('arco-theme');
window.localStorage.setItem('theme', t);
if (!theme) setTheme(t);
}, [theme]);
const tableList = useMemo(() => Object.values(tableDict || {}), [tableDict]);
return {
id,
tableList,
setQueryId,
tableDict,
setTableDict,
currentGraph,
setCurrentGraph,
linkDict,
setLinkDict,
box,
setBox,
name,
setName,
theme,
setTheme,
version,
setVersion,
editingTable,
setEditingTable,
editingField,
setEditingField,
addingField,
setAddingField,
saveCheck,
};
}
export default createContainer(useGraphState);
{
"WELCOME CHAT QUERY": "欢迎聊天查询",
"GET STRAT": "获取开始",
"Data Model": "数据模型",
"Create New ERD Model": "创建新的ERD模型",
"Create ERD Model from Example": "从示例中创建ERD模型",
"OR": "或者",
"createdAt": "创建时间",
"updatedAt": "更新时间",
"Please Enter": "请输入",
"Select Model to Create": "选择模型新建",
"Run": "运行",
"Execution Successful": "执行成功",
"Query Generated for You": "已为你生成查询"
}
\ No newline at end of file
{
"WELCOME CHAT QUERY": "欢迎使用 CHAT QUERY",
"GET START": "开始使用",
"Data Model": "数据模型",
"Create a new ERD model": "创建新的ERD模型",
"Create an ERD model from an example": "从示例中创建ERD模型",
"OR": "或",
"createdAt": "创建时间",
"updatedAt": "更新时间",
"Please enter": "请输入",
"Select a model to create a new one": "选择模型新建",
"Run": "运行",
"Execution successful": "执行成功",
"Query generated for you": "已为你生成查询",
"Welcome 👏": "欢迎你的到来 👏",
"Yes": "是",
"No": "否",
"CHAT QUERY": "CHAT QUERY",
"GET STRAT": "获取开始",
"Data Query Based on Data Model and AI": "基于数据模型和AI的数据查询",
"Save success": "保存成功",
"Save failed": "保存失败",
"Are you sure you want to delete this logs record?": "您确定要删除此日志记录吗?",
"Delete": "删除"
}
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clone the request headers
// You can modify them with headers API: https://developer.mozilla.org/en-US/docs/Web/API/Headers
const requestHeaders = new Headers(request.headers)
// Add new request headers
requestHeaders.set('authorization', `Bearer ${process.env.NEXT_PUBLIC_OPEN_AI_API_KEY || ''}`)
// You can also set request headers in NextResponse.rewrite
return NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})
}
\ No newline at end of file
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import zhCommon from './locales/zh/common.json';
import { map } from 'lodash';
i18n.use(LanguageDetector)
.use(initReactI18next)
.init({
// we init with resources
resources: {
zh: {
translations: {
...zhCommon,
},
modelList: {
'Example already exists or name conflicts': '示例已经存在或名称冲突',
'Create a new ERD model': '创建新的ERD模型',
'Create an ERD model from an example': '从示例中创建ERD模型',
'Are you sure to delete this graph?': '确定要删除此模型吗?',
'Edit Query': '编辑查询',
'Edit Model': '编辑模型',
Delete: '删除',
'Queries List': '查询列表',
},
ListNav: {
'Create Query': '创建查询',
'Import Database': '数据库导入',
'Import DBML': '导入DBML',
'Import DDL': '导入DDL',
'Create from Blank': '从空白新建',
'New Model': '新建模型',
'Import Example': '导入示例',
'Data Query Based on Data Model and AI': '基于数据模型和AI的数据查询',
'Created by AI': '由 AI 新建',
'AI was unable to understand your input, please further clarify your input.':
'AI未能理解您的输入, 请进一步明确您的输入。',
},
nav: {
'Apply Select Version': '应用所选版本',
'Exit Logs View': '退出日志视图',
Save: '保存',
'Add Table': '添加表',
'Import Table': '导入表',
'New Table': '新建表',
Clear: '清空',
Export: '导出',
Logs: '日志',
Yes: '是',
No: '否',
'Successfully added the following database tables': '已经成功新增以下数据表',
'According to your business needs, a new database model has been generated for you.':
'根据您提供的业务需求,已为您生成新的数据模型。',
'Generation failed, please modify the requirements and try again.':
'生成失败,请修改需求重试。',
'Table field information': '表字段信息',
'AI createTable': 'AI建表',
'Are you sure you want to delete all the tables?': '你是否确定删除所有表?',
},
graph: {
'Edit Table': '编辑表格',
Commit: '提交',
'Add field': '添加字段',
Yes: '是',
No: '否',
'Please enter table name': '请输入表格名称',
'Delete table': '删除表格',
'Are you sure you want to delete this table?': '确定要删除此表格吗?',
'have same name table': '存在同名表格',
'Add field after': '在后面添加字段',
'Please enter field name': '请输入字段名称',
'Please choose field type': '请选择字段类型',
Comment: '备注',
'Move up': '上移',
'Move down': '下移',
'Remove field': '删除字段',
'Table Name': '表名',
'Table Comment': '表注释',
Default: '默认值',
Type: '类型',
Name: '名称',
Type1: '字段类型',
Name1: '字段名称',
Cancel: '取消',
Edit: '编辑',
Field: '字段',
'Are you sure delete this field?': '您确定要删除这个字段吗?',
},
linkModal: {
'Change relation': '更改关系',
'Delete Path': '删除路径',
startPoint: '起始点',
endpoint: '结束点',
},
fieldForm: [
'Cancel',
'Please enter field name',
'Please choose field type',
'have same name field',
'Commit',
'Edit',
'Field',
'default',
'Default',
'Comment',
'Type',
],
ContextMenu: {
'Add New Table': '添加新表',
'Import Table': '导入表',
'Save Change': '保存更改',
'Export Database': '导出数据库',
},
tablesNav: {
'input table name': '输入表名称',
Tables: '表格',
},
table: {
Theme: '主题',
Clear: '清除',
Yes: '是',
No: '否',
Save: '保存',
'Do you want to save your changes before leaving?':
'你本次修改未保存,是否保存本次修改?',
'Are you sure you want to delete this table?': '您确定要删除此表吗?',
'Please Enter Comment': '请输入评论',
Commit: '提交',
'No Comment': '无评论',
'Are you sure you want to delete this field?': '您确定要删除此字段吗?',
Cancel: '取消',
Comment: '评论',
'Yes, restore them': '是的,恢复它们',
'No, ignore them': '不,忽略它们',
'Unsaved changes': '未保存的更改',
'You have some unsaved changes after last version, do you want to restore them? Once you press the no button, the unsaved changes will be cleaned immediately. You can’t undo this action.':
'您在上一个版本之后有一些未保存的更改,您想要恢复它们吗?一旦您按下“否”按钮,未保存的更改将立即被清除。您无法撤消此操作。',
},
actions: {
assistant: 'AI助手',
input: '输入',
'Please input your question': '请输入你的问题',
'Starting with "/", retrieve frequently asked questions':
'以“/”开头,快速获取常见问题',
'A query has been generated for you': '已为你生成查询',
run: '运行',
'Add into queries List': '添加到查询列表',
reset: '重置',
send: '发送',
'Select model to create': '选择模型新建',
'Execution succeeded': '执行成功',
'Analyzing requirements': '分析需求中',
'Creating query': '创建查询中',
'Hello! I an CHAT QUERY ai, please describe your business!':
'您好!我是 CHAT QUERY AI,请描述您的业务!',
'Connection Name': '连接名称',
Connection: '连接',
'Create a new database in the data source': '在数据源中创建新的数据库',
'Database Information': '数据库信息',
'New Database Name': '新建数据库名称',
'Query Information': '查询信息',
'Model Description': '模型描述',
'In AI Analysis Model': 'AI 分析模型中',
'Confirm deletion': '确定删除',
_QUERY_: '_查询_',
'Failed to generate query': '生成查询失败',
'Error in query': '错误查询',
},
modal: {
Import: '导入',
Close: '关闭',
'Import ERD Data Model': '导入ERD数据模型',
'Export ERD Data Model': '导出ERD数据模型',
'Copy Success': '复制成功',
'Copy Failed': '复制失败',
Copy: '复制',
'Import Failed': '导入失败',
'Test Connection': '测试连接',
Clear: '清空',
'Please Input Database Password': '请输入数据库密码',
'Please Input Database Name': '请输入数据库名称',
Password: '密码',
'Database Type': '数据库类型',
'Please Input Username': '请输入用户名',
Account: '账号',
'Please Select': '请选择',
'Connection Information': '连接信息',
'Please Input IP': '请输入IP',
'Please Input Port': '请输入端口',
'Connection Failed': '连接失败',
'Connection Succeeded': '连接成功',
},
queriesList: {
'Data Preview': '数据预览',
'Parameter check failed': '执行失败,请检查参数',
RUN: '运行',
'Deletion succeeded': '删除成功',
'Query Description': '查询描述',
'No description needed': '无需描述',
'Parameter Description': '参数描述',
'No parameters needed': '无需参数',
'Request Address': '请求地址',
'Execution Details': '执行详情',
'Edit Query': '编辑查询',
'Queries List': '查询列表',
},
actionForm: {
'please enter query name': '请输入查询名称',
name: '名称',
},
},
},
fallbackLng: 'en',
debug: true,
// have a common namespace used around the full app
ns: ['translations', 'modelList'],
defaultNS: 'translations',
keySeparator: false, // we use content as keys
interpolation: {
escapeValue: false,
},
});
export default i18n;
// const rewrites = () => {
// return [
// {
// source: '/openai/:path*',
// destination: 'https://chat-query-backend.netlify.app/api/openai/:path*',
// },
// ,
// ];
// };
const rewrites = async () => {
const ret = [
{
source: '/openai/:path*',
destination: `${process.env.OPENAI_PROXY_URL || 'https://api.openai.com'}/:path*`,
},
{
source: '/google-fonts/:path*',
destination: 'https://fonts.googleapis.com/:path*',
},
{
source: '/sharegpt',
destination: 'https://sharegpt.com/api/conversations',
},
{
source: '/backend/:path*',
// destination: 'https://chat-query-backend.onrender.com/:path*',
destination: `${process.env.NEXT_PUBLIC_BACKEND_URL}/:path*`,
// destination: 'http://139.198.179.193:30981/:path*',
},
];
return {
beforeFiles: ret,
};
};
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
publicRuntimeConfig: {
apiPath: '/api/',
backendOrigin: process.env.NEXT_PUBLIC_BACKEND_URL,
},
rewrites,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
};
module.exports = nextConfig;
{
"name": "v-query",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@arco-design/web-react": "^2.45.0",
"@dbml/core": "^2.5.1",
"@digitalocean/do-markdownit": "^1.9.0",
"@monaco-editor/react": "^4.4.5",
"@next/font": "13.1.6",
"@types/lodash": "^4.14.191",
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"autoprefixer": "^10.4.13",
"classnames": "^2.3.1",
"dexie": "^3.2.2",
"diff": "^5.1.0",
"echarts": "^5.4.2",
"echarts-for-react": "^3.0.2",
"eslint": "8.33.0",
"eslint-config-next": "13.1.6",
"i18next": "^22.5.0",
"i18next-browser-languagedetector": "^7.0.2",
"install": "^0.13.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"markdown-it-highlightjs": "^4.0.1",
"nanoid": "^4.0.0",
"next": "13.1.6",
"npm": "^9.6.0",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-preset-env": "^8.0.1",
"react": "18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-hook-speech-to-text": "^0.8.0",
"react-hotkeys-hook": "^4.3.7",
"react-i18next": "^12.3.1",
"react-live-runner": "^1.0.5",
"reactflow": "^11.5.6",
"redaxios": "^0.5.1",
"sass": "^1.50.0",
"styled-components": "6.0.0-rc.3",
"swr": "^2.0.3",
"tailwindcss": "^3.2.7",
"typescript": "4.9.5",
"unstated-next": "^1.1.0"
},
"resolutions": {
"postcss": "^8.4.14"
},
"devDependencies": {
"postcss": "^8.4.14",
"postcss-nesting": "^11.3.0"
},
"engines": {
"node": "18"
}
}
\ No newline at end of file
import '@arco-design/web-react/dist/css/arco.css';
import '../styles/globals.sass';
import '../styles/globals.css';
import '../styles/home.scss';
import '../styles/markdown/index.css';
import '../styles/markdown/index.scss';
import '../next-i18next.config.js';
import GraphContainer from '../hooks/use-graph-state';
import { ConfigProvider, ConfigContext } from '@arco-design/web-react';
import { useState } from 'react';
import { Spin } from '@arco-design/web-react';
import { useEffect } from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
function MyApp({ Component, pageProps }) {
const [local, setLocal] = useState();
const { i18n } = useTranslation();
useEffect(() => {
// import('@arco-design/web-react/es/locale/en-US').then(lang => {
// console.log(lang.default);
// setLocal(lang.default);
// });
// console.log(i18n.language);
if (i18n.language === 'en') {
import('@arco-design/web-react/es/locale/en-US').then(lang => {
setLocal(lang.default);
});
} else if (i18n.language === 'zh') {
import('@arco-design/web-react/es/locale/zh-CN').then(lang => {
setLocal(lang.default);
});
} else {
import('@arco-design/web-react/es/locale/zh-CN').then(lang => {
setLocal(lang.default);
});
}
}, [setLocal, i18n.language]);
return (
<GraphContainer.Provider>
{local ? (
<ConfigProvider locale={local}>
<Component {...pageProps} />
</ConfigProvider>
) : (
<div className="fixed inset-0 flex justify-center items-center">
<Spin dot />
</div>
)}
</GraphContainer.Provider>
);
}
export default MyApp;
import { Button, Card, Spin } from '@arco-design/web-react';
import { IconCode, IconEye, IconSend } from '@arco-design/web-react/icon';
import React, { ReactElement, memo, useCallback, useRef, useState } from 'react';
import { LiveEditor, LiveError, LivePreview, LiveProvider } from 'react-live-runner';
import * as styledComponents from 'styled-components';
import * as ReactECharts from 'echarts-for-react';
import AI from '@/components/AITool';
import useSWRMutation from 'swr/mutation';
import getView from '@/client/api/getView';
import { nanoid } from 'nanoid';
import { get } from 'lodash';
export function ChatView({
defaultNode,
props,
}: {
defaultNode?: ReactElement;
props: Record<string, any>;
}) {
const [showCode, setShowCode] = useState(true);
const reqRef = useRef(nanoid());
const { trigger, data, isMutating } = useSWRMutation(reqRef.current, (_, { arg: { need } }) => {
console.log(need);
return getView.getViewComponent({
props,
need,
});
});
const SendButton = useCallback(
({ inputRef }: { inputRef: any }) => {
return (
<Button
shape="circle"
icon={<IconSend />}
size="mini"
type="primary"
loading={isMutating}
onClick={async () => {
await trigger({ need: inputRef.current.dom.value });
// inputRef.current.dom.value = '';
}}
/>
);
},
[isMutating, trigger]
);
const example = get(data, 'data.code');
console.log(example, 'isMutating');
return (
<div>
<AI
simpleMode="input"
startView={3}
inputProps={{
style: { width: 400 },
height: 36,
className: 'overflow-hidden simple-mode-border mb-[20px]',
prefix: <span className="text-[var(--pc)]">组件显示</span>,
autoFocus: false,
}}
SendButton={SendButton}
/>
<Spin loading={isMutating} block className="my-[20px]">
{example ? (
<div>
<div className="text-[20px] absolute text-orange-200 z-[99] right-[20px]">
{showCode ? (
<IconCode onClick={() => setShowCode(false)} />
) : (
<IconEye onClick={() => setShowCode(true)} />
)}
</div>
<div className="overflow-hidden">
<LiveProvider
code={example}
scope={{
data: props,
import: {
react: React,
'styled-components': styledComponents,
'echarts-for-react': ReactECharts,
},
}}
>
{!showCode ? (
<LiveEditor className="overflow-auto min-h-[350px]" />
) : (
<LivePreview />
)}
<LiveError />
</LiveProvider>
</div>
</div>
) : (
defaultNode || null
)}
</Spin>
</div>
);
}
export default function View() {
return null;
}
import React from 'react';
import { Form, Input, Checkbox, Button, Radio } from '@arco-design/web-react';
import { useTranslation } from 'react-i18next';
const FormItem = Form.Item;
const RadioGroup = Radio.Group;
function ActionForm({ form, initialValues = {} }) {
const { t } = useTranslation('actionForm');
return (
<Form autoComplete="off" form={form} initialValues={initialValues}>
<FormItem label={t('name')} field="name" rules={[{ required: true }]}>
<Input style={{ width: 270 }} placeholder={t('please enter query name') || ''} />
</FormItem>
</Form>
);
}
export default ActionForm;
import AI from '@/components/AITool';
import Head from 'next/head';
import ListNav from '../../components/list_nav';
import {
Avatar,
Button,
Checkbox,
Dropdown,
Empty,
Form,
Grid,
Input,
Layout,
List,
Menu,
Message,
Modal,
Notification,
Space,
Table,
Tabs,
Typography,
Select,
} from '@arco-design/web-react';
import { memo, useCallback, useEffect, useMemo, useReducer, useState } from 'react';
import { getAllGraphs } from '@/data/db';
import {
IconClose,
IconCloseCircleFill,
IconDelete,
IconEdit,
IconLeft,
IconPlayArrow,
IconPlayArrowFill,
IconPlayCircle,
IconPlus,
IconRobotAdd,
IconSwap,
IconUnorderedList,
} from '@arco-design/web-react/icon';
import {
debounce,
each,
filter,
find,
first,
get,
isEqual,
map,
omit,
pick,
pickBy,
set,
size,
uniqueId,
values,
} from 'lodash';
import { nanoid } from 'nanoid';
import graphState from 'hooks/use-graph-state';
import exportSQL from 'utils/export-sql';
import ExecuteQuery from '@/client/api/executeQuery';
import { useSearchParams } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import Link from 'next/link';
import { DBForm } from '@/components/import_modal';
import useSWR from 'swr';
import ConnectDb, { DBInfo } from '@/client/api/connectDb';
import { backendApi } from '@/client/api';
import { types } from 'util';
import ActionForm from './actionForm';
import { DataTable, QueriesList } from './queriesList';
import { GET_QUERY, GET_SCHEMA_INFO } from '@/data/prompt';
import Welcome from 'components/AITool/MessageItem';
import * as queryTipRaw from '@/data/prompt/query-tip';
const queryTip = map(queryTipRaw, v => {
return {
name: v,
value: v,
};
});
const Content = Layout.Content;
const TabPane = Tabs.TabPane;
const FormItem = Form.Item;
const style = {
textAlign: 'center',
marginTop: 20,
};
type Session = {
id: string;
name: string;
modelId: string;
messageList: any[];
setMessageList: (value: any) => any;
};
function getQueryType(tagName: string) {
switch (tagName) {
case 'SQL':
return 'sql';
}
}
function AddDBForSchema({
showDbModel,
setShowDbModel,
addDbCallback,
form,
currentModelsDb,
}: Record<string, any>) {
const { t } = useTranslation('actions');
return (
<Modal
visible={showDbModel}
style={{
width: '600px',
}}
onCancel={() => {
setShowDbModel(false);
}}
onOk={addDbCallback}
>
<DBForm
form={form}
// initialValues={{}}
slot={
<>
{showDbModel ? (
<FormItem
label={t('Connection Name')}
field="name"
initialValue={t('Connection') + (currentModelsDb.length + 1)}
rules={[{ type: 'boolean', required: true }]}
>
<Input />
</FormItem>
) : null}
<FormItem
wrapperCol={{
span: 17,
offset: 5,
}}
field="create"
triggerPropName="checked"
initialValue={false}
rules={[{ type: 'boolean' }]}
>
<Checkbox>{t('Create a new database in the data source')}</Checkbox>
</FormItem>
<Form.Item shouldUpdate className="auto-h mb-[0]">
{({ create }) => {
return create ? (
<Form.Item
className="100%"
label={t('Database Information')}
field="client"
rules={[{ required: true }]}
>
<Form.Item
field="newDbName"
rules={[{ required: true }]}
className="mb-0"
>
<Input placeholder={t('New Database Name')} />
</Form.Item>
</Form.Item>
) : null;
}}
</Form.Item>
</>
}
/>
</Modal>
);
}
function DbList({
currentModelsDb,
setActiveDb,
mutate,
activeDb,
setShowDbModel,
setShowQueriesList,
}: any) {
return (
<>
{currentModelsDb.length
? map(currentModelsDb, (db, index) => (
<Avatar
key={index}
onClick={() => setActiveDb(db.id)}
triggerIcon={
<IconDelete
className="text-red-800"
onClick={async () => {
await ConnectDb.removeDbForSchema(db.id);
mutate();
}}
/>
}
className={`${
db.id === activeDb
? 'bg-[rgb(var(--primary-6))] shadow-slate-600'
: 'bg-slate-400 shadow-[rgba(0,0,0,0.5)] hover:shadow-[var(--pc)] hover:bg-[rgb(var(--primary-4))]'
} mb-[10px] shadow `}
>
{db.name}
</Avatar>
))
: null}
<Button
className="cursor-pointer shadow shadow-[var(--pc)]"
onClick={() => setShowDbModel(true)}
shape="circle"
size="large"
>
<IconPlus className="text-[rgb(var(--primary-6))]" />
</Button>
<div className="absolute bottom-[20px]">
<IconUnorderedList
onClick={() => setShowQueriesList(true)}
fontSize={25}
className="hover:text-[rgb(var(--primary-6))] text-[rgb(var(--primary-3))] cursor-pointer"
/>
</div>
</>
);
}
const EmptyPage = memo(
({ graphs, createModel }: { graphs: Record<string, any>; createModel: () => any }) => {
const { t } = useTranslation('actions');
return (
<div className="flex justify-center items-center flex-col pt-[150px] w-full">
<Empty />
<div>
{/* <Dropdown
droplist={
<Menu>
{map(graphs, ({ name, id }) => {
return (
<Menu.Item key={id} onClick={() => createModel(id, name)}>
{name}
</Menu.Item>
);
})}
</Menu>
}
> */}
<Button
type="primary"
icon={<IconPlus />}
onClick={createModel}
className="shadow"
>
{t('Select model to create')}
</Button>
{/* </Dropdown> */}
</div>
</div>
);
},
isEqual
);
EmptyPage.displayName = 'EmptyPage';
const MessageItemHOC = ({ setShowQueriesList, activeDb, currentModels, activeModelKey }: any) => {
const component = memo(
({ message, role, rawRender }: any) => {
const { t } = useTranslation('actions');
const [form] = Form.useForm();
if (role === 'user') {
return rawRender({ message, role });
}
const { sql, params, types, queryName, queryDescription } = useMemo<{
sql: any;
types: any;
params: Record<string, any>;
queryName: any;
queryDescription: any;
}>(() => {
const elNode = document.createElement('div');
elNode.innerHTML = message;
let sqlNodes = elNode.querySelectorAll('sql');
if (!sqlNodes.length) {
let str = message.replace(/```sql/g, `<sql>`);
str = str.replace(/```/g, `</sql>`);
elNode.innerHTML = str;
sqlNodes = elNode.querySelectorAll('sql');
}
const varNodes = elNode.querySelectorAll('var');
const varDescriptionNodes = elNode.querySelectorAll('varDescription');
const queryName = elNode.querySelector('queryName');
const queryDescription = elNode.querySelector('queryDescription');
return {
sql: map(sqlNodes, node => node.textContent?.trim()),
types: map(sqlNodes, node => getQueryType(node.tagName)),
params: pickBy(
Object.fromEntries(
map(varNodes, (node, index) => [
node.textContent?.trim(),
varDescriptionNodes[index]?.textContent?.trim() || '',
])
),
(_, key) => {
return /^\$(.*)\$$/.test(key);
}
),
queryName: queryName?.textContent?.trim(),
queryDescription: queryDescription?.textContent?.trim(),
};
}, [message]);
const [preViewData, setPreViewData] = useState([]);
const [rawSqlMode, switchrawSqlMode] = useState(false);
const [loading, setLoading] = useState(false);
const [modal, contextHolder] = Modal.useModal();
const [formInstance] = Form.useForm();
return (
<div
className={`flex gap-3 p-4 box-border shadow mx-[5px] rounded transition-colors mt-[20px] font-hm ${
role === 'user'
? 'bg-[rgb(var(--primary-6))] text-white'
: 'bg-[var(--white-bg)] text-[#333]'
}`}
>
<Form style={{ width: '100%' }} autoComplete="off" form={form}>
<div className="py-[10px] pb-[30px] text-[20px] justify-between flex">
{size(sql) ? (
<>
{t('A query has been generated for you')}
<span>
<IconSwap
onClick={() => {
switchrawSqlMode(!rawSqlMode);
}}
/>
</span>
</>
) : (
t('Failed to generate query')
)}
</div>
{rawSqlMode || !size(sql) ? (
<pre
className="break-before-all mb-[20px]"
style={{
whiteSpace: 'break-spaces',
}}
>
{message}
</pre>
) : (
map(params, (val, key) => {
console.log(val, key);
return (
<FormItem
label={key}
key={key}
field={key}
className={'form-item'}
>
<Input placeholder={`${t('input')}${val}`} />
</FormItem>
);
})
)}
{size(sql) ? (
[
<FormItem key="1">
<Button
type="primary"
loading={loading}
shape="round"
onClick={() => {
// ConnectDb.addQuery({
// schemaId: '',
// DbID: '',
// name: '',
// content: undefined,
// });
(modal as any).info({
icon: null,
title: t('Query Information'),
content: (
<ActionForm
form={formInstance}
initialValues={{ name: queryName }}
/>
),
onOk: async () => {
const modelId =
currentModels[activeModelKey || '']
?.modelId;
await ConnectDb.addQuery({
...(formInstance.getFields() as {
name: string;
}),
schemaId: modelId || '',
DbID: activeDb || '',
content: {
params: params || {},
executions: sql.map(
(sql: string, index: number) => {
return {
content: sql,
type: types[index],
};
}
),
info: {
queryDescription,
queryName,
},
},
});
setShowQueriesList(true);
},
});
}}
icon={<IconPlus />}
>
{t('Add into queries List')}
</Button>
<Button
className="mx-[20px] font-bold"
type="outline"
loading={loading}
shape="round"
onClick={() => {
// executeSql;
setLoading(true);
ExecuteQuery.executeSql(
form.getFields(),
sql.map((sql: string, index: number) => {
return {
content: sql,
type: types[index],
};
}),
activeDb
).then(data => {
setPreViewData(get(data, 'data.data'));
Notification.info({
closable: false,
title: t('Execution succeeded'),
content: '',
});
setLoading(false);
});
}}
icon={<IconPlayArrowFill />}
>
{t('run')}
</Button>
{contextHolder}
</FormItem>,
<div className="w-full" key="preViewData">
<DataTable data={preViewData} />
</div>,
]
) : (
<Empty
description={t('Error in query')}
icon={<IconCloseCircleFill className="text-red-500" />}
/>
)}
</Form>
</div>
);
},
(p: Object, n: Object) => {
return isEqual(pick(n, ['message', 'role']), pick(p, ['message', 'role']));
}
);
component.displayName = 'MessageItem';
return component;
};
export default function Actions(props) {
const [graphs, setGraphs] = useState([]);
const params = useSearchParams();
const defaultId = params.get('id');
const defaultName = params.get('name');
const { t } = useTranslation('actions');
const { t: ct } = useTranslation();
const [currentModels, setCurrentModels] = useState<
Record<
string,
{
id: string;
name: string;
modelId: string;
sessions: Record<string, Session>;
}
>
>({});
const [currentSessions, setCurrentSessions] = useState<Session[]>([]);
const [activeModelKey, setActiveModelKey] = useState<string>();
const [activeSessionKey, setActiveSessionKey] = useState<string>();
const [showDbModel, setShowDbModel] = useState(false);
const [activeDb, setActiveDb] = useState('');
const [showQueriesList, setShowQueriesList] = useState(false);
const { tableDict, linkDict, setQueryId, currentGraph } = graphState.useContainer();
const sql = exportSQL(tableDict, linkDict, 'dbml');
const [_, dispath] = useReducer((state: number) => {
setCurrentSessions([...currentSessions]);
return state++;
}, 0);
const createModel = useCallback(
(id: string, name: string) => {
const sessionId = nanoid();
const modelId = nanoid();
const nameId = uniqueId(name + t('_QUERY_'));
const session = {
id: sessionId,
name: nameId + '_1',
modelId: id,
messageList: [],
setMessageList: (messageList: any) => {
session.messageList = messageList;
dispath();
},
};
const newModels = {
...currentModels,
[modelId]: {
id: modelId,
name: nameId,
modelId: id,
sessions: {
[sessionId]: session,
},
},
};
setCurrentModels(newModels);
setCurrentSessions([session]);
setActiveSessionKey(sessionId);
//设置模型
setActiveModelKey(modelId);
setQueryId(id);
},
[
currentModels,
setCurrentModels,
setCurrentSessions,
setActiveSessionKey,
setCurrentSessions,
setQueryId,
]
);
const { data, mutate } = useSWR(defaultId, () => {
if (defaultId) {
return ConnectDb.getAllForSchema(defaultId);
}
});
const SchemaDescription = get(currentGraph, 'description');
const [form] = Form.useForm();
const currentModelsDb = useMemo(() => get(data, 'data', []), [data]);
const MessageItem = useMemo(() => {
return MessageItemHOC({ setShowQueriesList, activeDb, currentModels, activeModelKey });
}, [setShowQueriesList, activeDb, currentModels, activeModelKey]);
useEffect(() => {
if (defaultId && defaultName) {
const models = JSON.parse(localStorage.getItem(defaultId + '_' + 'models') || '{}');
if (size(models)) {
each(models, ({ sessions }) => {
each(sessions, session => {
session.setMessageList = (messageList: any) => {
session.messageList = messageList;
dispath();
};
});
});
setCurrentModels(models);
const model = first(values(models));
setCurrentSessions(values(model.sessions));
setActiveSessionKey(get(first(values(model.sessions)), 'id'));
setActiveModelKey(model.id);
} else {
createModel(defaultId, defaultName);
}
}
}, [defaultId]);
useEffect(() => {
const initGraphs = async () => {
try {
const data = await getAllGraphs();
if (data && data.length) {
data.sort((a, b) => b.createdAt - a.createdAt);
setGraphs(data);
}
} catch (e) {
console.log(e);
}
};
initGraphs();
}, []);
const saveSessions = useCallback(
debounce(() => {
if (defaultId) {
localStorage.setItem(defaultId + '_' + 'models', JSON.stringify(currentModels));
}
}, 500),
[defaultId, currentModels]
);
useEffect(() => {
saveSessions();
}, [currentSessions, saveSessions]);
useEffect(() => {
const session = find(currentSessions, { id: activeSessionKey });
if (!get(session, 'messageList.length') && sql) {
session?.setMessageList([
{
role: 'system',
content:
'IMPRTANT: You are a virtual assistant powered by the gpt-3.5-turbo model, now time is 2023/5/30 16:57:14}',
},
{
role: 'user',
content: GET_QUERY(sql),
},
{
role: 'assistant',
content: '好的,我明白了。请问你的业务是什么?',
},
]);
}
}, [activeSessionKey, sql, currentSessions]);
useEffect(() => {
if (data && !get(data, 'data', []).length) {
setShowDbModel(true);
}
}, [data]);
useEffect(() => {
setActiveDb(get(currentModelsDb, '[0].id'));
}, [currentModelsDb]);
const addDbCallback = async () => {
const config = form.getFields();
await ConnectDb.addDbForSchema({
name: config.name || '',
schemaId: defaultId || '',
config: config as DBInfo,
});
mutate();
setShowDbModel(false);
};
return (
<>
<Head>
<title>CHAT QUERY</title>
<meta name="description" content={ct('Data Query Based on Data Model and AI')} />
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
<ListNav customNode={<span></span>} />
<Link
href={`/actions/queriesList?id=${params.get('id')}&name=${params.get('name')}`}
passHref
className="fixed left-[20px] bottom-[20px] z-[99]"
>
<Button shape="circle" type="secondary" className="shadow-lg">
<IconLeft style={{ fontSize: 15 }} />
</Button>
</Link>
<Layout>
<div
className={'shadow-none pt-[70px] h-full flex items-start '}
style={{ width: 'calc(100%)' }}
>
{Object.keys(currentModels).length ? (
<Tabs
tabPosition="left"
editable
onDeleteTab={key => {
Modal.confirm({
title: t('Confirm deletion') + ' ?',
content: null,
okButtonProps: {
status: 'danger',
},
onOk: () => {
// currentModels;
const newModels = omit(currentModels, key);
setCurrentModels(newModels);
if (key === activeModelKey) {
const activeModel = first(values(newModels)) as {
sessions: any;
id: string;
};
const { sessions } = activeModel;
setCurrentSessions(values(sessions));
setActiveSessionKey(values(sessions)[0].id);
setActiveModelKey(activeModel.id);
}
},
});
}}
className="w-full"
onChange={key => {
const { sessions } = currentModels[key];
setCurrentSessions(values(sessions));
setActiveSessionKey(values(sessions)[0].id);
setActiveModelKey(key);
}}
activeTab={activeModelKey}
addButton={
<Button
type="primary"
icon={<IconPlus />}
size="mini"
onClick={() =>
defaultId && createModel(defaultId, defaultName || '')
}
/>
}
style={{
height: 'calc(100vh - 80px)',
}}
destroyOnHide={false}
>
{map(currentModels, modle => {
return (
<TabPane
key={modle.id}
title={modle.name}
destroyOnHide={false}
>
<Tabs
// editable
className="sessions—box"
type="card-gutter"
onChange={key => {
setActiveSessionKey(key);
}}
activeTab={activeSessionKey}
addButton={
<IconRobotAdd
fontSize={20}
className="text-[rgb(var(--primary-6))] hover:text-[rgb(var(--primary-4))]"
/>
}
>
{map(currentSessions, session => {
return (
<TabPane
key={session.id}
title={session.name}
>
<Content>
<AI
quickTip={queryTip}
welcome={
<>
<div className="my-4 flex rounded-sm text-[15px] font-bold text-[rgb(var(--primary-6))]">
{t(
'Model Description'
)}
:
</div>
{SchemaDescription ? (
<Welcome
message={
SchemaDescription
}
role={
'assistant'
}
/>
) : (
<Empty
description={
<span className="text-[var(--pc)] ml-[12px]">
{t(
'In AI Analysis Model'
) +
' ...'}
</span>
}
/>
)}
<Welcome
message={t(
'Hello! I an CHAT QUERY ai, please describe your business!'
)}
role={'assistant'}
/>
</>
}
startView={3}
key={session.id}
messageList={
session.messageList
}
setMessageList={
session.setMessageList
}
renderMessageItem={MessageItem}
/>
</Content>
</TabPane>
);
})}
</Tabs>
</TabPane>
);
})}
</Tabs>
) : (
<EmptyPage
graphs={graphs}
createModel={() =>
defaultId && createModel(defaultId, defaultName || '')
}
/>
)}
<div className="flex w-[70px] flex-wrap items-start content-start justify-center">
<DbList
{...{
currentModelsDb,
setActiveDb,
mutate,
activeDb,
setShowDbModel,
setShowQueriesList,
}}
/>
<QueriesList
visible={showQueriesList}
setVisible={setShowQueriesList}
modelId={currentModels[activeModelKey || '']?.modelId}
/>
<AddDBForSchema
showDbModel={showDbModel}
setShowDbModel={setShowDbModel}
addDbCallback={addDbCallback}
form={form}
currentModelsDb={currentModelsDb}
/>
</div>
</div>
</Layout>
</div>
</>
);
}
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
import {
Avatar,
Button,
Card,
Descriptions,
Drawer,
Empty,
Form,
Input,
Layout,
Link,
Notification,
Space,
Spin,
Typography,
} from '@arco-design/web-react';
import useSWR from 'swr';
import ConnectDb from '@/client/api/connectDb';
import { get, map, size } from 'lodash';
import {
IconDelete,
IconExpand,
IconPlayArrowFill,
IconThunderbolt,
} from '@arco-design/web-react/icon';
import { Table } from '@arco-design/web-react';
import Head from 'next/head';
import ListNav from '@/components/list_nav';
import { useSearchParams } from 'next/navigation';
import { codeWrapper, htmlString } from '@/components/AITool/MessageItem';
import { useTranslation } from 'react-i18next';
import getConfig from 'next/config';
import { ChatView } from './View/chatView';
const {
publicRuntimeConfig: { backendOrigin },
} = getConfig();
const FormItem = Form.Item;
export const DataTable = ({ data: preViewDataArr, loading }: any) => {
const { t } = useTranslation('queriesList');
return (
<div>
<div className="my-[10px] text-[15px] font-bold text-[rgb(var(--primary-6))]">
{t('Data Preview')}
</div>
{map(preViewDataArr, preViewData => {
return preViewData ? (
map(preViewData, (data, index) => {
return data ? (
<div>
<div>
<ChatView
props={data[0]}
defaultNode={
<Table
loading={loading}
className="w-full overflow-hidden mt-[20px]"
scroll={{
x: '100%',
y: 400,
}}
border={{
wrapper: true,
cell: true,
}}
columns={map(data[1], ({ name }, i) => {
return {
title: name,
dataIndex: name,
ellipsis: true,
};
})}
data={map(data[0], (item, index) => ({
key: index,
...item,
}))}
key={index}
/>
}
/>
</div>
</div>
) : null;
})
) : (
<Empty />
);
})}
</div>
);
};
function Item({
name,
i,
content,
params,
updatedAt,
id,
getListFx,
queryDescription,
}: {
name: string;
i: number;
params: Record<string, any>;
content: {
exections: {
content: string;
type: string;
};
};
updatedAt: string;
id: string;
getListFx: () => any;
queryDescription: string;
}) {
const [active, setActive] = useState(false);
const [formInstance] = Form.useForm();
const [previewData, setPreviewData] = useState([]);
const [isRun, setRunning] = useState(false);
const { t } = useTranslation('queriesList');
return (
<Card
className="card-with-icon-hover mb-[10px] rounded drop-shadow cursor-pointer hover:border-[rgb(var(--primary-6))] border-2"
style={{ width: '100%' }}
actions={[
<Button
key="0"
loading={isRun}
type="primary"
size="mini"
shape="round"
className="font-bold"
icon={<IconPlayArrowFill />}
onClick={async () => {
let data;
try {
setRunning(true);
data = await ConnectDb.runQuery(id, formInstance.getFields());
setRunning(false);
} catch (err) {
Notification.error({
content: t('Parameter check failed'),
});
}
setPreviewData(get(data, 'data.data', []));
setActive(true);
}}
>
{t('RUN')}
</Button>,
<Button
key="1"
size="mini"
shape="round"
status="danger"
icon={<IconDelete />}
onClick={async () => {
await ConnectDb.deleteQuery(id);
Notification.success({
content: t('Deletion succeeded'),
duration: 1000,
});
getListFx();
}}
/>,
<Button
key="2"
size="mini"
shape="round"
status={active ? 'success' : 'default'}
onClick={() => setActive(!active)}
icon={<IconExpand />}
/>,
]}
>
<Card.Meta
// className={`${active ? 'shadow p-[10px]' : ''}`}
avatar={
<Space>
<Avatar size={15}>YOU</Avatar>
<span className="text-[12px] text-[rgb(var(--primary-6))]">
{new Date(updatedAt).toLocaleString()}
</span>
</Space>
}
description={
<div className="my-[10px] ">
<Typography.Paragraph className=" h-full !m-0 my-[5px] text-[rgb(var(--primary-6))] font-bold text-[12px] inline-block">
{t('Query Description')}
</Typography.Paragraph>
{queryDescription ? (
<div className="my-[5px] text-[12px] indent-[2em]">
{queryDescription}
</div>
) : (
<Empty description={t('No description needed')} icon={<span></span>} />
)}
<Typography.Paragraph
className=" h-full !m-0 my-[5px] text-[rgb(var(--primary-6))] font-bold text-[12px] inline-block"
copyable={{
text: JSON.stringify({ params: formInstance.getFields() }),
}}
>
{t('Parameter Description')}
</Typography.Paragraph>
{Object.keys(params).length ? (
<Form style={{ width: '100%' }} autoComplete="off" form={formInstance}>
{map(params, (val, key) => {
return (
<FormItem
label={key}
key={key}
field={key}
className={'form-item'}
initialValue={''}
>
<Input placeholder={`${val}`} />
</FormItem>
);
})}
</Form>
) : (
<Empty description={t('No parameters needed')} icon={<span></span>} />
)}
</div>
}
title={
<div className=" items-center text-[rgb(var(--primary-6))] font-bold justify-start">
<div className="mr-[20px] mb-[10px]">{name}</div>
<Input
style={{
width: '100%',
// border: '1px solid rgb(var(--primary-6))',
}}
readOnly
className="shadow-lg text-[rgb(var(--primary-6))]"
value={`${backendOrigin}/query/run/${id}?type=1`}
disabled
addBefore={
<span className="text-[#ccc] text-[14px]">
{t('Request Address')}
</span>
}
addAfter={
<Typography.Paragraph
className="flex items-center h-full !m-0"
copyable={{
text: `${backendOrigin}/query/run/${id}?type=1`,
}}
></Typography.Paragraph>
}
/>
</div>
}
/>
{active ? (
<div className="mt-[20px] border-t border-solid">
{/* mx-[-15px] px-[15px] */}
<Descriptions
border
size="mini"
title={
<div className="my-[10px] text-[rgb(var(--primary-6))] font-bold text-[15px]">
{t('Execution Details')}
</div>
}
column={1}
data={map(content, ({ type, content }) => ({
label: type,
value: (
<div
className="message prose text-slate break-words overflow-hidden"
dangerouslySetInnerHTML={{
__html: htmlString(codeWrapper(type, content)),
}}
/>
),
}))}
style={{ marginBottom: 20 }}
labelStyle={{ paddingRight: 36 }}
/>
<DataTable data={previewData} loading={isRun} />
</div>
) : null}
</Card>
);
}
export function QueriesList({
visible,
setVisible,
modelId,
}: {
visible: boolean;
setVisible: Dispatch<SetStateAction<boolean>>;
modelId: string;
}) {
const { t } = useTranslation('queriesList');
return (
<Drawer
width={600}
title={<span>{t('Queries List')}</span>}
visible={visible}
onOk={() => {
setVisible(false);
}}
onCancel={() => {
setVisible(false);
}}
footer={null}
autoFocus={false}
>
<QueriesRender modelId={modelId} refresh={visible} />
</Drawer>
);
}
function QueriesRender({ modelId, refresh }: { modelId: string; refresh?: boolean }) {
const { data, isLoading, mutate } = useSWR('all_query_for_Schema', () => {
return modelId && ConnectDb.getQueries(modelId);
});
useEffect(() => {
if (refresh) mutate && mutate();
}, [refresh]);
useEffect(() => {
modelId && mutate && mutate();
}, [modelId]);
const renderData = get(data, 'data', []);
const params = useSearchParams();
const { t } = useTranslation('queriesList');
return (
<Spin loading={isLoading} className="block min-h-[500px]">
{size(renderData) ? (
map(renderData, ({ name, content, updatedAt, id }, index) => (
<Item
name={name}
i={index}
updatedAt={updatedAt}
content={get(content, 'executions', [])}
params={get(content, 'params', {})}
queryDescription={get(content, 'info.queryDescription')}
id={id}
getListFx={mutate}
key={index}
/>
))
) : (
<div className="flex justify-center items-center flex-col">
<Empty className="mt-[40px] m-auto" />
<Link
href={`/actions?id=${params.get('id')}&name=${params.get('name')}`}
className="!bg-transparent mt-[20px]"
>
<Button
type="primary"
icon={<IconThunderbolt />}
onClick={() => {}}
size="large"
>
{t('Edit Query')}
</Button>
</Link>
</div>
)}
</Spin>
);
}
export default function QueriesPage() {
const params = useSearchParams();
const { t } = useTranslation('queriesList');
const { t: ct } = useTranslation();
return (
<>
<Head>
<title>CHAT QUERY</title>
<meta name="description" content={ct('Data Query Based on Data Model and AI')} />
<link rel="icon" href="/favicon.ico" />
</Head>
<ListNav
customNode={[
<Link
key="0"
href={`/actions?id=${params.get('id')}&name=${params.get('name')}`}
className="!bg-transparent"
>
<Button
size="small"
type="primary"
icon={<IconThunderbolt />}
shape="round"
onClick={() => {}}
>
{t('Edit Query')}
</Button>
</Link>,
]}
importGraph={undefined}
addGraph={undefined}
addExample={undefined}
importDBML={undefined}
handlerImportGraph={undefined}
/>
<Layout className="overflow-auto mt-[85px]">
<div
className="w-[85%] m-auto"
style={{
height: 'calc(100vh - 120px)',
}}
>
<QueriesRender modelId={params.get('id')} />
</div>
</Layout>
</>
);
}
import Head from 'next/head';
import dynamic from 'next/dynamic';
import { useEffect, useMemo, useRef, useState } from 'react';
import { nanoid } from 'nanoid';
import { useHotkeys } from 'react-hotkeys-hook';
import TableForm from '../../components/table_form';
import FieldForm from '../../components/field_form';
import LinkPath from '../../components/link_path';
import LinkModal from '../../components/link_modal';
import Nav from '../../components/nav';
import Table from '../../components/table';
import TableNav from '../../components/table_nav';
import ContextMenu from '../../components/context_menu';
import LogsDrawer from '../../components/logs';
import graphState from '../../hooks/use-graph-state';
import tableModel from '../../hooks/table-model';
import { get } from 'lodash';
import { includes } from 'lodash';
import { some } from 'lodash';
import { reverse } from 'lodash';
import { useTranslation } from 'react-i18next';
const ExportModal = dynamic(() => import('../../components/export_modal'), {
ssr: false,
});
const ImportModal = dynamic(() => import('../../components/import_modal'), {
ssr: false,
});
export default function Home() {
const {
tableList,
tableDict,
setTableDict,
linkDict,
setLinkDict,
box,
setBox,
name,
version,
saveCheck,
} = graphState.useContainer();
const { updateGraph, addTable } = tableModel();
// console.log(linkDict);
const links = useMemo(() => Object.values(linkDict || {}), [linkDict]);
const svg = useRef();
// ''|dragging|moving|linking
const [mode, setMode] = useState('');
// offset of svg origin
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [movingTable, setMovingTable] = useState();
const [showModal, setShowModal] = useState('');
const [showDrawer, setShowDrawer] = useState('');
const [formChange, setFormChange] = useState(false);
const [editingLink, setEditingLink] = useState(null);
const [tableSelectedId, setTableSelectId] = useState(null);
const [linkStat, setLinkStat] = useState({
startX: null,
startY: null,
startTableId: null,
startField: null,
endX: null,
endY: null,
});
/**
* It sets the offset to the mouse position relative to the box, and sets the mode to 'draging'
*/
const mouseDownHandler = e => {
if (e.target.tagName === 'svg' && e.button !== 2) {
setOffset({
x: box.x + (e.clientX * box.w) / global.innerWidth,
y: box.y + (e.clientY * box.h) / global.innerHeight,
});
setMode('draging');
}
};
/**
* It sets the moving table to the table that was clicked on, and sets the mode to moving
* @param e - the event object
* @param table - the table object that was clicked on
*/
const tableMouseDownHandler = (e, table) => {
if (e.button === 2 || version !== 'currentVersion') return;
const { x: cursorX, y: cursorY } = getSVGCursor(e);
setMovingTable({
id: table.id,
offsetX: cursorX - table.x,
offsetY: cursorY - table.y,
});
setMode('moving');
e.preventDefault();
// e.stopPropagation();
};
/**
* When the user releases the mouse button, if the user was in linking mode, and the user is not
* linking the same table to itself, then add a new link to the link dictionary
*/
const mouseUpHandler = e => {
if (mode === 'linking') {
const row = e.target.classList.contains('row') ? e.target : e.target.closest('.row');
if (row) {
const endTableId = row.getAttribute('tableid');
const endField = row.getAttribute('fieldid');
if (
!links.find(
link =>
[
`${link.endpoints[0].id} ${link.endpoints[0].fieldId}`,
`${link.endpoints[1].id} ${link.endpoints[1].fieldId}`,
]
.sort()
.join(' ') ===
[
`${linkStat.startTableId} ${linkStat.startField}`,
`${endTableId} ${endField}`,
]
.sort()
.join(' ')
)
) {
setLinkDict(state => {
const id = nanoid();
return {
...state,
[id]: {
id,
name: null,
endpoints: [
{
id: linkStat.startTableId,
// tableName: "sales",
// fieldNames: ["id"],
fieldId: linkStat.startField,
relation: '1',
},
{
id: endTableId,
// tableName: "store",
// fieldNames: ["id"],
fieldId: endField,
relation: '*',
},
],
},
};
});
}
}
}
setMode('');
setLinkStat({
startX: null,
startY: null,
startTableId: null,
startField: null,
endX: null,
endY: null,
});
setMovingTable(null);
};
/**
* It takes a mouse event and returns the cursor position in SVG coordinates
* @returns The cursor position in the SVG coordinate system.
*/
const getSVGCursor = ({ clientX, clientY }) => {
let point = svg.current.createSVGPoint();
point.x = clientX;
point.y = clientY;
return point.matrixTransform(svg.current.getScreenCTM().inverse());
};
/**
* > When the mouse is moving, if the mode is 'draging', then update the box state with the new x
* and y values. If the mode is 'moving', then update the tableDict state with the new x and y
* values. If the mode is 'linking', then update the linkStat state with the new endX and endY
* values
*/
const mouseMoveHandler = e => {
if (!mode) return;
if (mode === 'draging') {
setBox(state => {
return {
w: state.w,
h: state.h,
x: offset.x - e.clientX * (state.w / global.innerWidth),
y: offset.y - e.clientY * (state.h / global.innerHeight),
clientH: state.clientH,
clientW: state.clientW,
};
});
}
if (mode === 'moving') {
const { x: cursorX, y: cursorY } = getSVGCursor(e);
setTableDict(state => {
return {
...state,
[movingTable.id]: {
...state[movingTable.id],
x: cursorX - movingTable.offsetX,
y: cursorY - movingTable.offsetY,
},
};
});
}
if (mode === 'linking') {
const { x, y } = getSVGCursor(e);
setLinkStat({
...linkStat,
endX: x,
endY: y + 3,
});
}
};
/**
* `wheelHandler` is a function that takes an event object as an argument and returns a function
* that takes a state object as an argument and returns a new state object
*/
const wheelHandler = e => {
let { deltaY, deltaX } = e;
const cursor = getSVGCursor(e);
if (!e.ctrlKey) {
setBox(state => {
if (state.w > 4000 && deltaY > 0) return state;
if (state.w < 600 && deltaY < 0) return state;
return {
x: state.x + deltaX,
y: state.y + deltaY,
w: state.w,
h: state.h,
clientH: state.clientH,
clientW: state.clientW,
};
});
} else {
setBox(state => {
if (state.w > 4000 && deltaY > 0) return state;
if (state.w < 600 && deltaY < 0) return state;
deltaY = deltaY * 2;
deltaX = deltaY * (state.w / state.h);
const deltaLimit = 600;
if (deltaY > deltaLimit) {
deltaY = deltaY > deltaLimit ? deltaLimit : deltaY;
deltaX = deltaY * (state.w / state.h);
} else if (deltaY < -deltaLimit) {
deltaY = deltaY < -deltaLimit ? -deltaLimit : deltaY;
deltaX = deltaY * (state.w / state.h);
}
return {
x: state.x - ((cursor.x - state.x) / state.w) * deltaX,
y: state.y - ((cursor.y - state.y) / state.h) * deltaY,
w: state.w + deltaX,
h: state.h + deltaY,
clientH: state.clientH,
clientW: state.clientW,
};
});
}
e.preventDefault();
};
useEffect(() => {
const instance = svg.current;
instance.addEventListener('wheel', wheelHandler, { passive: false });
return () => {
instance.removeEventListener('wheel', wheelHandler, {
passive: false,
});
};
}, [version]);
/**
* It sets the linkStat object to the current mouse position and the table and field that the mouse
* is over
*/
const gripMouseDownHandler = e => {
if (version !== 'currentVersion') return;
const { x, y } = getSVGCursor(e);
const row = e.currentTarget.closest('.row');
setLinkStat({
...linkStat,
startX: x,
startY: y,
startTableId: row.getAttribute('tableid'),
startField: row.getAttribute('fieldid'),
});
setMode('linking');
e.preventDefault();
e.stopPropagation();
};
const handlerTableSelected = table => {
// 表位置移动后的 x/y 会表现不一致
const svgInfo = svg.current.getBBox();
setBox(state => {
const newX = table.x + svgInfo.x - (table.x > -16 ? 264 : -table.x / 2);
return {
w: state.w,
h: state.h,
x: newX,
y: svgInfo.y + table.y + (svgInfo.y < 0 ? 88 : -72),
clientH: state.clientH,
clientW: state.clientW,
};
});
setTableSelectId(table.id);
};
useHotkeys('ctrl+s, meta+s', () => updateGraph(), { preventDefault: true }, [
tableDict,
linkDict,
name,
]);
useHotkeys('ctrl+n, meta+n', () => addTable(), { preventDefault: true }, [tableDict, linkDict]);
useHotkeys('ctrl+e, meta+e', () => setShowModal('export'), { preventDefault: true }, [
tableDict,
linkDict,
]);
useHotkeys('ctrl+i, meta+i', () => setShowModal('import'), { preventDefault: true });
useHotkeys('ctrl+h, meta+h', () => setShowDrawer('logs'), { preventDefault: true });
useHotkeys(
'ctrl+=, meta+=',
() => {
setBox(state => ({
x: state.x + state.w * 0.05,
y: state.y + state.h * 0.05,
w: state.w * 0.9,
h: state.h * 0.9,
clientH: state.clientH,
clientW: state.clientW,
}));
},
{ preventDefault: true }
);
useHotkeys(
'ctrl+-, meta+-',
() => {
setBox(state => ({
x: state.x - state.w * 0.05,
y: state.y - state.h * 0.05,
w: state.w * 1.1,
h: state.h * 1.1,
clientH: state.clientH,
clientW: state.clientW,
}));
},
{ preventDefault: true }
);
useHotkeys(
'ctrl+0, meta+0',
() => {
setBox(state => ({
x: 0,
y: 0,
w: state.clientW,
h: state.clientH,
clientH: state.clientH,
clientW: state.clientW,
}));
},
{ preventDefault: true }
);
useHotkeys(
['ctrl+a', 'meta+a'],
() => {
const svgInfo = svg.current.getBBox();
setBox(state => ({
x: 0,
y: svgInfo.y - 72,
w: svgInfo.width,
h: svgInfo.height,
clientH: state.clientH,
clientW: state.clientW,
}));
},
{ preventDefault: true }
);
// const [currentHoverTable, setCurrentHoverTable] = useState();
const [currentHoverField, setCurrentHoverField] = useState();
const { t } = useTranslation();
const importDBML = useRef();
return (
<div className="graph">
<Head>
<title>CHAT QUERY</title>
<meta name="description" content={t('Data Query Based on Data Model and AI')} />
<link rel="icon" href="/favicon.ico" />
</Head>
<Nav
setShowModal={setShowModal}
setShowDrawer={setShowDrawer}
leave={saveCheck}
importDBML={importDBML}
/>
<ContextMenu setShowModal={setShowModal}>
<svg
className="main"
viewBox={`${box.x} ${box.y} ${box.w} ${box.h}`}
onMouseDown={mouseDownHandler}
onMouseUp={mouseUpHandler}
onMouseMove={mouseMoveHandler}
// onWheel={wheelHandler}
ref={svg}
>
{reverse(links).map(link => {
// console.log(link, currentHoverField, tableSelectedId);
let isActive = false;
if (
(isActive = some(get(link, 'endpoints'), v => v.id === tableSelectedId))
) {
isActive = true;
}
if (currentHoverField) {
isActive = some(
get(link, 'endpoints'),
v => v.fieldId === currentHoverField
);
}
return (
<LinkPath
isActive={isActive}
link={link}
key={`${link.id}`}
setEditingLink={setEditingLink}
/>
);
})}
{tableList.map(table => {
return (
<Table
setCurrentHoverField={setCurrentHoverField}
key={table.id}
table={table}
onTableMouseDown={tableMouseDownHandler}
onGripMouseDown={gripMouseDownHandler}
tableSelectedId={tableSelectedId}
setTableSelectId={setTableSelectId}
/>
);
})}
<rect x="0" y="0" width="2" height="2"></rect>
{mode === 'linking' &&
version === 'currentVersion' &&
linkStat.startX != null &&
linkStat.endX != null && (
<line
x1={linkStat.startX}
y1={linkStat.startY}
x2={linkStat.endX}
y2={linkStat.endY}
stroke="red"
strokeDasharray="5,5"
/>
)}
</svg>
</ContextMenu>
<TableForm formChange={formChange} onFormChange={setFormChange} />
<FieldForm formChange={formChange} onFormChange={setFormChange} />
<LinkModal editingLink={editingLink} setEditingLink={setEditingLink} />
<ImportModal
showModal={showModal}
onCloseModal={() => setShowModal('')}
importDBML={importDBML}
/>
<ExportModal showModal={showModal} onCloseModal={() => setShowModal('')} />
<LogsDrawer showDrawer={showDrawer} onCloseDrawer={() => setShowDrawer('')} />
<TableNav
onTableSelected={handlerTableSelected}
setTableSelectId={setTableSelectId}
tableSelectedId={tableSelectedId}
/>
</div>
);
}
import Head from 'next/head';
import Link from 'next/link';
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
import {
List,
Button,
Empty,
Space,
Avatar,
Popconfirm,
Notification,
Divider,
Tag,
} from '@arco-design/web-react';
import {
IconEdit,
IconDelete,
IconNav,
IconCalendarClock,
IconCopy,
IconThunderbolt,
IconFolderAdd,
IconPlus,
} from '@arco-design/web-react/icon';
import { useState, useEffect } from 'react';
import { addGraph, delGraph, getAllGraphs } from '../../data/db';
import ListNav from '../../components/list_nav';
import northwindTraders from '../../data/example/northwind_traders.json';
import blog from '../../data/example/blog.json';
import spaceX from '../../data/example/spacex.json';
import { redirect } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import { map } from 'lodash';
import { Dropdown } from '@arco-design/web-react';
import { Menu } from '@arco-design/web-react';
import { get } from 'lodash';
import { useRef } from 'react';
const ImportModal = dynamic(() => import('../../components/import_modal'), { ssr: false });
/**
* It fetches all the graphs from the database and displays them in a list
* @returns Home component
*/
export default function Home() {
const router = useRouter();
const [graphs, setGraphs] = useState([]);
const [showModal, setShowModal] = useState('');
const [importType, setImportType] = useState('');
const { t } = useTranslation('translations');
const { t: st } = useTranslation('modelList');
const { t: nt } = useTranslation('ListNav');
useEffect(() => {
const initGraphs = async () => {
try {
const data = await getAllGraphs();
if (data && data.length) {
data.sort((a, b) => b.createdAt - a.createdAt);
setGraphs(data);
}
} catch (e) {
console.log(e);
}
};
initGraphs();
}, []);
const deleteGraph = async id => {
await delGraph(id);
setGraphs(state => state.filter(item => item.id !== id));
};
const handlerImportGraph = async ({ tableDict, linkDict, name }) => {
const id = await addGraph({
tableDict,
linkDict,
name: name || `Untitled graph ${graphs.length}`,
});
router.push(`/graphs/${id}`);
};
const handlerAddGraph = async name => {
const id = await addGraph({ name: `Untitled graph ${graphs.length}` });
router.push(`/graphs/${id}`);
};
const handlerAddExample = async () => {
try {
await Promise.all(
[northwindTraders, blog, spaceX].map(({ id, ...item }) => addGraph(item, id))
);
} catch (error) {
Notification.error({
title: st('Example already exists or name conflicts'),
});
return;
}
setGraphs(state => [northwindTraders, blog, spaceX, ...state]);
Notification.success({
title: st('Sample data generated success.'),
});
};
function importGraph(type) {
setShowModal('import');
setImportType(type);
}
// 传递导入方法
const importDBML = useRef();
return (
<>
<Head>
<title>CHAT QUERY</title>
<meta name="description" content={t('Data Query Based on Data Model and AI')} />
<link rel="icon" href="/favicon.ico" />
</Head>
<ListNav
addGraph={() => handlerAddGraph()}
importGraph={importGraph}
addExample={() => handlerAddExample()}
handlerImportGraph={handlerImportGraph} // AI
importDBML={importDBML}
/>
<div className="graph-container">
{graphs && graphs.length ? (
<List
className="graph-list shadow-lg graphs-list"
size="large"
header={t('Data Model')}
dataSource={graphs}
render={(item, index) => (
<List.Item
className="flex"
key={item.id}
extra={
<Space className="flex items-center h-full">
<Link
href={`/actions/queriesList?id=${item.id}&name=${item.name}`}
>
<Button
type="primary"
icon={<IconThunderbolt />}
shape="round"
className="shadow"
onClick={() => {}}
>
{st('Queries List')}
</Button>
</Link>
<Link href={`/graphs/${item.id}`}>
<Button
type="secondary"
icon={<IconEdit />}
shape="round"
className="shadow"
>
{st('Edit Model')}
</Button>
</Link>
<Popconfirm
title={st('Are you sure to delete this graph?')}
okText={t('Yes')}
cancelText={t('No')}
position="br"
onOk={() => deleteGraph(item.id)}
>
<Button
type="primary"
status="danger"
shape="round"
className="shadow"
icon={<IconDelete />}
>
{st('Delete')}
</Button>
</Popconfirm>
</Space>
}
>
<List.Item.Meta
avatar={
<Avatar shape="square">
{get(item, 'name[0]', 'unknow')}
</Avatar>
}
title={item.name}
description={
<Space style={{ marginTop: 4 }}>
<Tag color="arcoblue" icon={<IconNav />}>
{Object.keys(item.tableDict || {}).length} tables
</Tag>
<Tag color="green" icon={<IconCopy />}>
{t('createdAt')}
{new Date(item.createdAt).toLocaleString()}
</Tag>
<Tag color="gold" icon={<IconCalendarClock />}>
{t('updatedAt')}
{new Date(item.updatedAt).toLocaleString()}
</Tag>
</Space>
}
/>
</List.Item>
)}
/>
) : (
<div className="tc">
<Empty style={{ marginBottom: 16 }} />
<Dropdown
key={0}
trigger={'hover'}
droplist={
<Menu className="w-[200px]">
<Menu.Item key="3" onClick={() => importGraph(3)}>
{nt('Import Database')}
</Menu.Item>
<Menu.Item key="0" onClick={() => importGraph(1)}>
{nt('Import DBML')}
</Menu.Item>
<Menu.Item key="2" onClick={() => importGraph(2)}>
{nt('Import DDL')}
</Menu.Item>
<Menu.Item key="1" onClick={() => handlerAddGraph()}>
{nt('Create from Blank')}
</Menu.Item>
</Menu>
}
>
<Button size="large" type="primary" onClick={() => handlerAddGraph()}>
<IconPlus /> {st('Create a new ERD model')}
</Button>
</Dropdown>
<Divider orientation="center">{t('OR')}</Divider>
<Button size="large" type="outline" onClick={() => handlerAddExample()}>
{st('Create an ERD model from an example')}
</Button>
</div>
)}
</div>
<ImportModal
showModal={showModal}
onCloseModal={() => setShowModal('')}
cb={args => handlerImportGraph(args)}
type={importType}
importDBML={importDBML}
/>
</>
);
}
import Head from 'next/head';
import useSWR from 'swr';
import getSchema from '@/client/api/getSchema';
import { get } from 'lodash';
import { List, Avatar, Layout, Typography } from '@arco-design/web-react';
import '@arco-design/web-react/dist/css/arco.css';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import Link from 'next/link';
import { useTranslation, Trans } from 'react-i18next';
function Home() {
const { data } = useSWR('getTableList', () => getSchema.getTableList({ type: 'schema' }));
const { t } = useTranslation();
return (
<>
<Head>
<title>CHAT QUERY</title>
<meta name="description" content={t('Data Query Based on Data Model and AI')} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="home bg-scroll">
<Layout className="main">
<Layout>
<div className="container">
<h1 className="bg-gradient-to-r from-orange-500 bg-lime-400 bg-clip-text text-transparent">
<svg
className="text-stroke"
viewBox="0 0 500 100"
width="80%"
height="100%"
>
<defs>
<linearGradient
id="linear-gradient"
cx="50%"
cy="50%"
r="50%"
fx="50%"
fy="50%"
>
<stop
offset="0%"
style={{
stopColor: 'rgba(249, 115, 22,.6)',
}}
/>
<stop
offset="100%"
style={{
stopColor: 'rgb(163 230 53)',
}}
/>
</linearGradient>
<linearGradient
id="linear-gradient1"
cx="50%"
cy="50%"
r="50%"
fx="50%"
fy="50%"
>
<stop
offset="0%"
style={{
stopColor: 'rgba(163,230,53,0.8)',
}}
/>
<stop
offset="100%"
style={{
stopColor: 'rgba(249, 115, 22,.6)',
}}
/>
</linearGradient>
<radialGradient
id="linear-gradient3"
cx="50%"
cy="50%"
r="50%"
fx="50%"
fy="50%"
>
<stop
offset="0%"
style={{
stopColor: 'rgb(163 230 53)',
}}
/>
<stop
offset="40%"
style={{
stopColor: 'rgba(249, 115, 22,.6)',
}}
/>
<stop
offset="80%"
style={{
stopColor: 'rgb(163 230 53)',
}}
/>
<stop
offset="100%"
style={{
stopColor: 'rgba(249, 115, 22,.6)',
}}
/>
</radialGradient>
</defs>
<text className="text" x="40" y="60">
{t('WELCOME CHAT QUERY')}
</text>
</svg>
<span className="gradient-text-animation bg-clip-text text-transparent text-[45px]">
🎉
</span>
</h1>
<Link
href={{
pathname: '/graphs',
}}
>
<div
className="box px-4 py-2 font-semibold text-sm bg-gradient-to-r from-orange-300 bg-lime-300 hover:bg-orange-300 hover:from-lime-300 ease-in-out duration-150 flex items-center justify-center m-auto mt-6 underline decoration-transparent rounded-[10px] cursor-pointer shadow shadow-orange-100 hover:rounded-2xl text-[var(--pc)] opacity-80 ring ring-transparent hover:ring-orange-400 hover:ring-[4px] ring-inset hover:text-[rgba(255,255,255)] text-[16px] "
style={{
transition: 'all 0.8s',
}}
>
{t('GET STRAT')}
</div>
</Link>
</div>
</Layout>
</Layout>
</main>
</>
);
}
export default function App() {
return (
<DndProvider backend={HTML5Backend}>
<Home />
</DndProvider>
);
}
This source diff could not be displayed because it is too large. You can view the blob instead.
module.exports = {
plugins: {
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
},
};
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 643.3875090623186 178.55006903019566"
height="109.76296914086143"
width="395.52">
<g>
<rect width="8.054155239886452" height="155.03623938946075"
x="416.6041760206452" y="11.756914820367456" fill="#0074d9" opacity="1"
stroke-width="0" stroke="transparent" fill-opacity="1" class="rect-o-0"
data-fill-palette-color="primary" rx="1%" id="o-0"
data-palette-color="#0074d9"></rect>
</g>
<g transform="matrix(1,0,0,1,0,24.67660143615585)">
<svg viewBox="0 0 395.52000000000004 129.19686615788396"
height="129.19686615788396" width="395.52000000000004">
<g>
<svg viewBox="0 0 395.52000000000004 129.19686615788396"
height="129.19686615788396" width="395.52000000000004">
<g>
<svg viewBox="0 0 395.52000000000004 89.31096774193549"
height="89.31096774193549" width="395.52000000000004">
<g transform="matrix(1,0,0,1,0,0)">
<svg width="395.52000000000004"
viewBox="0 -50 193.74000549316406 43.75"
height="89.31096774193549"
data-palette-color="#0074d9">
<path
d="M31.25-6.25L0-6.25 0-50 31.25-50 31.25-43.75 37.5-43.75 37.5-37.5 43.75-37.5 43.75-18.75 37.5-18.75 37.5-12.5 31.25-12.5 31.25-6.25ZM12.5-43.75L12.5-12.5 25-12.5 25-18.75 31.25-18.75 31.25-37.5 25-37.5 25-43.75 12.5-43.75ZM87.5-6.25L50-6.25 50-50 87.5-50 87.5-43.75 93.75-43.75 93.75-31.25 87.5-31.25 87.5-25 93.75-25 93.75-12.5 87.5-12.5 87.5-6.25ZM62.5-43.75L62.5-31.25 81.25-31.25 81.25-43.75 62.5-43.75ZM62.5-25L62.5-12.5 81.25-12.5 81.25-25 62.5-25ZM143.75-6.25L100-6.25 100-50 143.75-50 143.75-43.75 112.5-43.75 112.5-31.25 137.5-31.25 137.5-25 112.5-25 112.5-12.5 143.75-12.5 143.75-6.25ZM162.49-6.25L149.99-6.25 149.99-50 187.49-50 187.49-43.75 193.74-43.75 193.74-25 181.24-25 181.24-18.75 187.49-18.75 187.49-12.5 193.74-12.5 193.74-6.25 174.99-6.25 174.99-12.5 168.74-12.5 168.74-18.75 162.49-18.75 162.49-6.25ZM162.49-43.75L162.49-25 174.99-25 174.99-31.25 181.24-31.25 181.24-43.75 162.49-43.75Z"
opacity="1" transform="matrix(1,0,0,1,0,0)"
fill="#0074d9" class="undefined-text-0"
data-fill-palette-color="primary"
id="text-0"></path>
</svg>
</g>
</svg>
</g>
<g
transform="matrix(1,0,0,1,163.31020800000002,110.39514376258064)">
<svg viewBox="0 0 232.20979200000002 18.801722395303326"
height="18.801722395303326" width="232.20979200000002">
<g transform="matrix(1,0,0,1,0,0)">
<svg width="232.20979200000002"
viewBox="3.5 -38.150001525878906 613.1300048828125 49.650001525878906"
height="18.801722395303326"
data-palette-color="#0074d9">
<path
d="M3.5-36.45L10.85-36.45 19.45-13.35Q19.95-12.05 20.35-10.78 20.75-9.5 21.15-7.95L21.15-7.95Q21.55-9.5 21.9-10.73 22.25-11.95 22.75-13.2L22.75-13.2 31.3-36.45 37.9-36.45 37.9 0 32.85 0 32.85-26.05Q32.85-26.95 32.93-27.8 33-28.65 33.05-29.65L33.05-29.65Q32.75-28.85 32.4-28 32.05-27.15 31.75-26.25L31.75-26.25 22.15 0.05 19.15 0.05 9.4-26.45Q9.1-27.25 8.78-28.1 8.45-28.95 8.2-29.65L8.2-29.65Q8.3-28.75 8.33-27.93 8.35-27.1 8.35-26.25L8.35-26.25 8.35 0 4.2 0 4.2-33.2 3.5-36.45ZM54.55-25.45L54.55-25.45Q58.5-25.45 60.55-23.33 62.6-21.2 62.6-17.25L62.6-17.25 62.6-5.15Q62.6-4 63-3.6 63.4-3.2 64-3.2L64-3.2Q64.45-3.2 64.95-3.33 65.45-3.45 65.9-3.65L65.9-3.65 66.8-0.8Q65.95-0.3 64.7 0.08 63.45 0.45 62.15 0.45L62.15 0.45Q60.75 0.45 59.7-0.28 58.65-1 58.35-2.95L58.35-2.95Q56.9-1.3 54.97-0.38 53.05 0.55 50.85 0.55L50.85 0.55Q47.9 0.55 45.92-1.18 43.95-2.9 43.95-5.95L43.95-5.95Q43.95-8.8 45.3-10.5 46.65-12.2 48.82-13.08 51-13.95 53.45-14.25 55.9-14.55 58.05-14.55L58.05-14.55 58.05-17.35Q58.05-19.4 57.2-20.7 56.35-22 54-22L54-22Q52.15-22 50.42-21.23 48.7-20.45 47.05-19.05L47.05-19.05 44.9-22.25Q47-23.85 49.47-24.65 51.95-25.45 54.55-25.45ZM58.05-9.6L58.05-11.1Q56.75-11.1 55.15-11 53.55-10.9 52.05-10.43 50.55-9.95 49.57-8.95 48.6-7.95 48.6-6.2L48.6-6.2Q48.6-4.65 49.7-3.93 50.8-3.2 52.35-3.2L52.35-3.2Q54.05-3.2 55.32-4.15 56.6-5.1 57.32-6.58 58.05-8.05 58.05-9.6L58.05-9.6ZM69.85-38.15L75.1-38.15 75.1-14.6 84.05-24.85 88.8-24.85 79.55-14.4 90.15 0 84.85 0 75.1-13.35 75.1 0 70.5 0 70.5-35.3 69.85-38.15ZM102.24-25.45L102.24-25.45Q105.34-25.45 107.42-24.18 109.49-22.9 110.54-20.68 111.59-18.45 111.59-15.6L111.59-15.6Q111.59-14.9 111.52-14.15 111.44-13.4 111.29-12.55L111.29-12.55 95.59-12.55Q95.59-8 97.67-5.5 99.74-3 103.14-3L103.14-3Q104.59-3 106.19-3.45 107.79-3.9 109.49-4.85L109.49-4.85 111.04-2.15Q109.34-0.75 107.04-0.05 104.74 0.65 102.54 0.65L102.54 0.65Q98.64 0.65 96.02-1.15 93.39-2.95 92.09-5.9 90.79-8.85 90.79-12.15L90.79-12.15Q90.79-14.7 91.52-17.05 92.24-19.4 93.69-21.33 95.14-23.25 97.27-24.35 99.39-25.45 102.24-25.45ZM101.89-22.1L101.89-22.1Q100.19-22.1 98.92-21.18 97.64-20.25 96.89-18.8 96.14-17.35 95.89-15.85L95.89-15.85 106.84-15.85Q106.94-17.8 106.34-19.2 105.74-20.6 104.62-21.35 103.49-22.1 101.89-22.1ZM138.29-25.4L138.29-25.4Q139.89-25.4 141.56-24.9 143.24-24.4 144.44-23.3L144.44-23.3Q144.39-24.05 144.36-24.73 144.34-25.4 144.34-26.05L144.34-26.05 144.34-35.25 143.64-38.15 148.84-38.15 148.84-7.15Q148.84-6.1 148.99-4.8 149.14-3.5 149.49-2.27 149.84-1.05 150.34-0.1L150.34-0.1 145.59 0.45Q145.29-0.35 145.04-1.2 144.79-2.05 144.69-2.85L144.69-2.85Q143.44-1.2 141.46-0.28 139.49 0.65 137.44 0.65L137.44 0.65Q134.84 0.65 132.86-0.45 130.89-1.55 129.61-3.43 128.34-5.3 127.69-7.58 127.04-9.85 127.04-12.2L127.04-12.2Q127.04-15.65 128.29-18.68 129.54-21.7 132.01-23.55 134.49-25.4 138.29-25.4ZM138.44-21.85L138.44-21.85Q136.59-21.85 135.31-21 134.04-20.15 133.29-18.75 132.54-17.35 132.21-15.73 131.89-14.1 131.89-12.5L131.89-12.5Q131.89-10.85 132.26-9.22 132.64-7.6 133.41-6.23 134.19-4.85 135.51-4.03 136.84-3.2 138.79-3.2L138.79-3.2Q140.44-3.2 141.89-4.15 143.34-5.1 144.34-6.35L144.34-6.35 144.34-14.7Q144.34-16.6 143.76-18.23 143.19-19.85 141.89-20.85 140.59-21.85 138.44-21.85ZM165.54-25.45L165.54-25.45Q169.49-25.45 171.54-23.33 173.59-21.2 173.59-17.25L173.59-17.25 173.59-5.15Q173.59-4 173.99-3.6 174.39-3.2 174.99-3.2L174.99-3.2Q175.44-3.2 175.94-3.33 176.44-3.45 176.89-3.65L176.89-3.65 177.79-0.8Q176.94-0.3 175.69 0.08 174.44 0.45 173.14 0.45L173.14 0.45Q171.74 0.45 170.69-0.28 169.64-1 169.34-2.95L169.34-2.95Q167.89-1.3 165.96-0.38 164.04 0.55 161.84 0.55L161.84 0.55Q158.89 0.55 156.91-1.18 154.94-2.9 154.94-5.95L154.94-5.95Q154.94-8.8 156.29-10.5 157.64-12.2 159.81-13.08 161.99-13.95 164.44-14.25 166.89-14.55 169.04-14.55L169.04-14.55 169.04-17.35Q169.04-19.4 168.19-20.7 167.34-22 164.99-22L164.99-22Q163.14-22 161.41-21.23 159.69-20.45 158.04-19.05L158.04-19.05 155.89-22.25Q157.99-23.85 160.46-24.65 162.94-25.45 165.54-25.45ZM169.04-9.6L169.04-11.1Q167.74-11.1 166.14-11 164.54-10.9 163.04-10.43 161.54-9.95 160.56-8.95 159.59-7.95 159.59-6.2L159.59-6.2Q159.59-4.65 160.69-3.93 161.79-3.2 163.34-3.2L163.34-3.2Q165.04-3.2 166.31-4.15 167.59-5.1 168.31-6.58 169.04-8.05 169.04-9.6L169.04-9.6ZM184.43-32L187.73-32Q187.38-30.25 187.11-28.43 186.83-26.6 186.73-24.85L186.73-24.85 193.13-24.85 192.28-21.55 186.73-21.55 186.73-6.1Q186.73-5.2 186.93-4.48 187.13-3.75 187.68-3.33 188.23-2.9 189.28-2.9L189.28-2.9Q190.08-2.9 190.88-3.05 191.68-3.2 192.43-3.4L192.43-3.4 193.13-0.4Q191.88-0.05 190.66 0.25 189.43 0.55 187.98 0.55L187.98 0.55Q184.78 0.55 183.48-1.13 182.18-2.8 182.18-5.9L182.18-5.9 182.18-21.55 178.38-21.55 178.38-24.2Q180.38-24.25 181.58-25.53 182.78-26.8 183.43-28.6 184.08-30.4 184.43-32L184.43-32ZM206.73-25.45L206.73-25.45Q210.68-25.45 212.73-23.33 214.78-21.2 214.78-17.25L214.78-17.25 214.78-5.15Q214.78-4 215.18-3.6 215.58-3.2 216.18-3.2L216.18-3.2Q216.63-3.2 217.13-3.33 217.63-3.45 218.08-3.65L218.08-3.65 218.98-0.8Q218.13-0.3 216.88 0.08 215.63 0.45 214.33 0.45L214.33 0.45Q212.93 0.45 211.88-0.28 210.83-1 210.53-2.95L210.53-2.95Q209.08-1.3 207.16-0.38 205.23 0.55 203.03 0.55L203.03 0.55Q200.08 0.55 198.11-1.18 196.13-2.9 196.13-5.95L196.13-5.95Q196.13-8.8 197.48-10.5 198.83-12.2 201.01-13.08 203.18-13.95 205.63-14.25 208.08-14.55 210.23-14.55L210.23-14.55 210.23-17.35Q210.23-19.4 209.38-20.7 208.53-22 206.18-22L206.18-22Q204.33-22 202.61-21.23 200.88-20.45 199.23-19.05L199.23-19.05 197.08-22.25Q199.18-23.85 201.66-24.65 204.13-25.45 206.73-25.45ZM210.23-9.6L210.23-11.1Q208.93-11.1 207.33-11 205.73-10.9 204.23-10.43 202.73-9.95 201.76-8.95 200.78-7.95 200.78-6.2L200.78-6.2Q200.78-4.65 201.88-3.93 202.98-3.2 204.53-3.2L204.53-3.2Q206.23-3.2 207.51-4.15 208.78-5.1 209.51-6.58 210.23-8.05 210.23-9.6L210.23-9.6ZM234.28-25.35L234.28-25.35Q236.83-25.35 238.78-24.25 240.73-23.15 242-21.28 243.28-19.4 243.93-17.15 244.58-14.9 244.58-12.55L244.58-12.55Q244.58-9.15 243.35-6.15 242.13-3.15 239.65-1.25 237.18 0.65 233.43 0.65L233.43 0.65Q231.33 0.65 229.4-0.15 227.48-0.95 226.13-2.6L226.13-2.6 225.18 0.05 222.58 0.05Q222.63-0.8 222.68-1.68 222.73-2.55 222.73-3.35L222.73-3.35 222.73-35.3 222.03-38.15 227.28-38.15 227.28-24.7Q227.28-24.05 227.25-23.55 227.23-23.05 227.18-22.6L227.18-22.6Q228.78-24.05 230.45-24.7 232.13-25.35 234.28-25.35ZM232.98-21.6L232.98-21.6Q231.38-21.6 229.88-20.83 228.38-20.05 227.28-18.9L227.28-18.9 227.28-10Q227.28-8 227.85-6.33 228.43-4.65 229.75-3.65 231.08-2.65 233.28-2.65L233.28-2.65Q235.13-2.65 236.35-3.63 237.58-4.6 238.3-6.08 239.03-7.55 239.35-9.22 239.68-10.9 239.68-12.35L239.68-12.35Q239.68-13.95 239.33-15.55 238.98-17.15 238.18-18.53 237.38-19.9 236.1-20.75 234.83-21.6 232.98-21.6ZM259.33-25.45L259.33-25.45Q263.28-25.45 265.33-23.33 267.38-21.2 267.38-17.25L267.38-17.25 267.38-5.15Q267.38-4 267.78-3.6 268.18-3.2 268.78-3.2L268.78-3.2Q269.23-3.2 269.73-3.33 270.23-3.45 270.68-3.65L270.68-3.65 271.58-0.8Q270.73-0.3 269.48 0.08 268.23 0.45 266.93 0.45L266.93 0.45Q265.53 0.45 264.48-0.28 263.43-1 263.13-2.95L263.13-2.95Q261.68-1.3 259.75-0.38 257.83 0.55 255.63 0.55L255.63 0.55Q252.68 0.55 250.7-1.18 248.73-2.9 248.73-5.95L248.73-5.95Q248.73-8.8 250.08-10.5 251.43-12.2 253.6-13.08 255.78-13.95 258.23-14.25 260.68-14.55 262.83-14.55L262.83-14.55 262.83-17.35Q262.83-19.4 261.98-20.7 261.13-22 258.78-22L258.78-22Q256.93-22 255.2-21.23 253.48-20.45 251.83-19.05L251.83-19.05 249.68-22.25Q251.78-23.85 254.25-24.65 256.73-25.45 259.33-25.45ZM262.83-9.6L262.83-11.1Q261.53-11.1 259.93-11 258.33-10.9 256.83-10.43 255.33-9.95 254.35-8.95 253.38-7.95 253.38-6.2L253.38-6.2Q253.38-4.65 254.48-3.93 255.58-3.2 257.13-3.2L257.13-3.2Q258.83-3.2 260.1-4.15 261.38-5.1 262.1-6.58 262.83-8.05 262.83-9.6L262.83-9.6ZM283.52-25.45L283.52-25.45Q285.22-25.45 286.97-25.05 288.72-24.65 290.17-23.75L290.17-23.75 288.57-20.35Q287.52-21.2 286.17-21.65 284.82-22.1 283.52-22.1L283.52-22.1Q281.77-22.1 280.47-21.33 279.17-20.55 279.17-18.9L279.17-18.9Q279.17-17.15 280.32-16.3 281.47-15.45 283.12-14.7L283.12-14.7Q284.37-14.1 285.65-13.5 286.92-12.9 288.02-12.05 289.12-11.2 289.8-9.95 290.47-8.7 290.47-6.85L290.47-6.85Q290.47-4.3 289.2-2.63 287.92-0.95 285.9-0.15 283.87 0.65 281.57 0.65L281.57 0.65Q279.57 0.65 277.5 0.1 275.42-0.45 273.72-1.45L273.72-1.45 275.57-4.7Q276.77-3.85 278.25-3.28 279.72-2.7 281.17-2.7L281.17-2.7Q282.97-2.7 284.35-3.58 285.72-4.45 285.72-6.25L285.72-6.25Q285.72-7.45 284.97-8.25 284.22-9.05 283.07-9.68 281.92-10.3 280.67-10.85L280.67-10.85Q279.17-11.55 277.72-12.4 276.27-13.25 275.35-14.6 274.42-15.95 274.42-18.1L274.42-18.1Q274.42-20.55 275.75-22.18 277.07-23.8 279.15-24.63 281.22-25.45 283.52-25.45ZM306.32-25.45L306.32-25.45Q309.42-25.45 311.5-24.18 313.57-22.9 314.62-20.68 315.67-18.45 315.67-15.6L315.67-15.6Q315.67-14.9 315.6-14.15 315.52-13.4 315.37-12.55L315.37-12.55 299.67-12.55Q299.67-8 301.75-5.5 303.82-3 307.22-3L307.22-3Q308.67-3 310.27-3.45 311.87-3.9 313.57-4.85L313.57-4.85 315.12-2.15Q313.42-0.75 311.12-0.05 308.82 0.65 306.62 0.65L306.62 0.65Q302.72 0.65 300.1-1.15 297.47-2.95 296.17-5.9 294.87-8.85 294.87-12.15L294.87-12.15Q294.87-14.7 295.6-17.05 296.32-19.4 297.77-21.33 299.22-23.25 301.35-24.35 303.47-25.45 306.32-25.45ZM305.97-22.1L305.97-22.1Q304.27-22.1 303-21.18 301.72-20.25 300.97-18.8 300.22-17.35 299.97-15.85L299.97-15.85 310.92-15.85Q311.02-17.8 310.42-19.2 309.82-20.6 308.7-21.35 307.57-22.1 305.97-22.1ZM342.37-25.4L342.37-25.4Q343.97-25.4 345.64-24.9 347.32-24.4 348.52-23.3L348.52-23.3Q348.47-24.05 348.44-24.73 348.42-25.4 348.42-26.05L348.42-26.05 348.42-35.25 347.72-38.15 352.92-38.15 352.92-7.15Q352.92-6.1 353.07-4.8 353.22-3.5 353.57-2.27 353.92-1.05 354.42-0.1L354.42-0.1 349.67 0.45Q349.37-0.35 349.12-1.2 348.87-2.05 348.77-2.85L348.77-2.85Q347.52-1.2 345.54-0.28 343.57 0.65 341.52 0.65L341.52 0.65Q338.92 0.65 336.94-0.45 334.97-1.55 333.69-3.43 332.42-5.3 331.77-7.58 331.12-9.85 331.12-12.2L331.12-12.2Q331.12-15.65 332.37-18.68 333.62-21.7 336.09-23.55 338.57-25.4 342.37-25.4ZM342.52-21.85L342.52-21.85Q340.67-21.85 339.39-21 338.12-20.15 337.37-18.75 336.62-17.35 336.29-15.73 335.97-14.1 335.97-12.5L335.97-12.5Q335.97-10.85 336.34-9.22 336.72-7.6 337.49-6.23 338.27-4.85 339.59-4.03 340.92-3.2 342.87-3.2L342.87-3.2Q344.52-3.2 345.97-4.15 347.42-5.1 348.42-6.35L348.42-6.35 348.42-14.7Q348.42-16.6 347.84-18.23 347.27-19.85 345.97-20.85 344.67-21.85 342.52-21.85ZM370.41-25.45L370.41-25.45Q373.51-25.45 375.59-24.18 377.66-22.9 378.71-20.68 379.76-18.45 379.76-15.6L379.76-15.6Q379.76-14.9 379.69-14.15 379.61-13.4 379.46-12.55L379.46-12.55 363.76-12.55Q363.76-8 365.84-5.5 367.91-3 371.31-3L371.31-3Q372.76-3 374.36-3.45 375.96-3.9 377.66-4.85L377.66-4.85 379.21-2.15Q377.51-0.75 375.21-0.05 372.91 0.65 370.71 0.65L370.71 0.65Q366.81 0.65 364.19-1.15 361.56-2.95 360.26-5.9 358.96-8.85 358.96-12.15L358.96-12.15Q358.96-14.7 359.69-17.05 360.41-19.4 361.86-21.33 363.31-23.25 365.44-24.35 367.56-25.45 370.41-25.45ZM370.06-22.1L370.06-22.1Q368.36-22.1 367.09-21.18 365.81-20.25 365.06-18.8 364.31-17.35 364.06-15.85L364.06-15.85 375.01-15.85Q375.11-17.8 374.51-19.2 373.91-20.6 372.79-21.35 371.66-22.1 370.06-22.1ZM393.81-25.45L393.81-25.45Q395.51-25.45 397.26-25.05 399.01-24.65 400.46-23.75L400.46-23.75 398.86-20.35Q397.81-21.2 396.46-21.65 395.11-22.1 393.81-22.1L393.81-22.1Q392.06-22.1 390.76-21.33 389.46-20.55 389.46-18.9L389.46-18.9Q389.46-17.15 390.61-16.3 391.76-15.45 393.41-14.7L393.41-14.7Q394.66-14.1 395.94-13.5 397.21-12.9 398.31-12.05 399.41-11.2 400.09-9.95 400.76-8.7 400.76-6.85L400.76-6.85Q400.76-4.3 399.49-2.63 398.21-0.95 396.19-0.15 394.16 0.65 391.86 0.65L391.86 0.65Q389.86 0.65 387.79 0.1 385.71-0.45 384.01-1.45L384.01-1.45 385.86-4.7Q387.06-3.85 388.54-3.28 390.01-2.7 391.46-2.7L391.46-2.7Q393.26-2.7 394.64-3.58 396.01-4.45 396.01-6.25L396.01-6.25Q396.01-7.45 395.26-8.25 394.51-9.05 393.36-9.68 392.21-10.3 390.96-10.85L390.96-10.85Q389.46-11.55 388.01-12.4 386.56-13.25 385.64-14.6 384.71-15.95 384.71-18.1L384.71-18.1Q384.71-20.55 386.04-22.18 387.36-23.8 389.44-24.63 391.51-25.45 393.81-25.45ZM412.41-33.35L412.41-33.35Q412.41-32.05 411.48-31.1 410.56-30.15 409.16-30.15L409.16-30.15Q407.86-30.15 406.93-31.1 406.01-32.05 406.01-33.35L406.01-33.35Q406.01-34.65 406.93-35.6 407.86-36.55 409.21-36.55L409.21-36.55Q410.51-36.55 411.46-35.6 412.41-34.65 412.41-33.35ZM406.36-24.85L411.51-24.85 411.51 0 406.96 0 406.96-21.95 406.36-24.85ZM426.51-25.5L426.51-25.5Q427.96-25.5 429.26-25.13 430.56-24.75 431.96-24.75L431.96-24.75Q433.91-24.75 435.93-24.98 437.96-25.2 440.11-25.5L440.11-25.5 439.16-21.75 435.11-21.75Q434.76-21.75 434.41-21.8 434.06-21.85 433.71-21.9L433.71-21.9Q434.56-20.8 434.98-19.58 435.41-18.35 435.41-17.05L435.41-17.05Q435.41-14.55 434.26-12.55 433.11-10.55 430.96-9.43 428.81-8.3 425.86-8.3L425.86-8.3Q424.71-8.3 423.81-7.68 422.91-7.05 422.91-5.75L422.91-5.75Q422.91-4.9 423.41-4.53 423.91-4.15 424.66-4.1 425.41-4.05 426.16-4.05L426.16-4.05 430.51-4.05Q431.86-4.05 433.26-3.88 434.66-3.7 435.86-3.18 437.06-2.65 437.78-1.6 438.51-0.55 438.51 1.25L438.51 1.25Q438.51 4 437.38 5.95 436.26 7.9 434.38 9.15 432.51 10.4 430.18 10.95 427.86 11.5 425.51 11.5L425.51 11.5Q424.11 11.5 422.51 11.2 420.91 10.9 419.51 10.18 418.11 9.45 417.23 8.25 416.36 7.05 416.36 5.2L416.36 5.2Q416.36 3.25 417.56 1.75 418.76 0.25 420.36-0.7L420.36-0.7 420.36-0.9Q419.31-1.35 418.66-2.33 418.01-3.3 418.01-4.45L418.01-4.45Q418.01-5.05 418.51-5.6 419.01-6.15 419.81-7.1 420.61-8.05 421.51-9.7L421.51-9.7Q419.71-10.9 418.63-12.78 417.56-14.65 417.56-16.85L417.56-16.85Q417.56-19.3 418.78-21.25 420.01-23.2 422.06-24.35 424.11-25.5 426.51-25.5ZM426.51-22.3L426.51-22.3Q424.76-22.3 423.53-20.73 422.31-19.15 422.31-16.85L422.31-16.85Q422.31-14.5 423.53-12.93 424.76-11.35 426.46-11.35L426.46-11.35Q428.41-11.35 429.56-12.88 430.71-14.4 430.71-16.8L430.71-16.8Q430.71-19.15 429.48-20.73 428.26-22.3 426.51-22.3ZM429.26 0.05L423.56 0.05Q422.61 0.7 421.81 1.93 421.01 3.15 421.01 4.3L421.01 4.3Q421.01 5.5 421.86 6.3 422.71 7.1 423.96 7.5 425.21 7.9 426.26 7.9L426.26 7.9Q427.96 7.9 429.83 7.43 431.71 6.95 433.01 5.83 434.31 4.7 434.31 2.7L434.31 2.7Q434.31 1.4 433.48 0.85 432.66 0.3 431.51 0.18 430.36 0.05 429.26 0.05L429.26 0.05ZM441.65-24.65L446.6-25.3Q446.95-24.45 447.18-23.58 447.4-22.7 447.5-21.8L447.5-21.8Q449-23.25 451.05-24.35 453.1-25.45 455.2-25.45L455.2-25.45Q458-25.45 459.65-24.33 461.3-23.2 462.05-21.23 462.8-19.25 462.8-16.65L462.8-16.65 462.8 0 458.25 0 458.25-15.45Q458.25-16.9 458.05-18.32 457.85-19.75 457.03-20.65 456.2-21.55 454.3-21.55L454.3-21.55Q453.2-21.55 451.98-21.08 450.75-20.6 449.68-19.88 448.6-19.15 447.8-18.35L447.8-18.35 447.8 0 443.3 0 443.3-17.65Q443.3-18.6 443.15-19.93 443-21.25 442.65-22.55 442.3-23.85 441.65-24.65L441.65-24.65ZM490.35-25.45L490.35-25.45Q492.05-25.45 493.8-25.05 495.55-24.65 497-23.75L497-23.75 495.4-20.35Q494.35-21.2 493-21.65 491.65-22.1 490.35-22.1L490.35-22.1Q488.6-22.1 487.3-21.33 486-20.55 486-18.9L486-18.9Q486-17.15 487.15-16.3 488.3-15.45 489.95-14.7L489.95-14.7Q491.2-14.1 492.47-13.5 493.75-12.9 494.85-12.05 495.95-11.2 496.62-9.95 497.3-8.7 497.3-6.85L497.3-6.85Q497.3-4.3 496.02-2.63 494.75-0.95 492.72-0.15 490.7 0.65 488.4 0.65L488.4 0.65Q486.4 0.65 484.32 0.1 482.25-0.45 480.55-1.45L480.55-1.45 482.4-4.7Q483.6-3.85 485.07-3.28 486.55-2.7 488-2.7L488-2.7Q489.8-2.7 491.17-3.58 492.55-4.45 492.55-6.25L492.55-6.25Q492.55-7.45 491.8-8.25 491.05-9.05 489.9-9.68 488.75-10.3 487.5-10.85L487.5-10.85Q486-11.55 484.55-12.4 483.1-13.25 482.17-14.6 481.25-15.95 481.25-18.1L481.25-18.1Q481.25-20.55 482.57-22.18 483.9-23.8 485.97-24.63 488.05-25.45 490.35-25.45ZM508.95-33.35L508.95-33.35Q508.95-32.05 508.02-31.1 507.1-30.15 505.7-30.15L505.7-30.15Q504.4-30.15 503.47-31.1 502.55-32.05 502.55-33.35L502.55-33.35Q502.55-34.65 503.47-35.6 504.4-36.55 505.75-36.55L505.75-36.55Q507.05-36.55 508-35.6 508.95-34.65 508.95-33.35ZM502.9-24.85L508.05-24.85 508.05 0 503.5 0 503.5-21.95 502.9-24.85ZM514.39-24.7L519.34-25.25Q519.69-24.45 519.92-23.58 520.14-22.7 520.24-21.85L520.24-21.85Q521.69-23.2 523.59-24.33 525.49-25.45 527.59-25.45L527.59-25.45Q529.89-25.45 531.59-24.5 533.29-23.55 534.29-21.4L534.29-21.4Q535.84-22.95 537.87-24.2 539.89-25.45 542.14-25.45L542.14-25.45Q544.89-25.45 546.52-24.3 548.14-23.15 548.87-21.2 549.59-19.25 549.59-16.65L549.59-16.65 549.59 0 545.04 0 545.04-15.55Q545.04-17 544.82-18.38 544.59-19.75 543.74-20.65 542.89-21.55 541.09-21.55L541.09-21.55Q539.44-21.55 537.79-20.48 536.14-19.4 535.04-18.3L535.04-18.3 535.04 0 530.49 0 530.49-15.55Q530.49-17 530.27-18.38 530.04-19.75 529.22-20.65 528.39-21.55 526.54-21.55L526.54-21.55Q525.54-21.55 524.42-21.05 523.29-20.55 522.29-19.8 521.29-19.05 520.54-18.35L520.54-18.35 520.54 0 515.99 0 515.99-18Q515.99-19 515.82-20.23 515.64-21.45 515.29-22.65 514.94-23.85 514.39-24.7L514.39-24.7ZM555.84-24.65L560.64-25.25Q561.04-24.45 561.24-23.6 561.44-22.75 561.54-21.95L561.54-21.95Q562.84-23.55 564.76-24.5 566.69-25.45 568.79-25.45L568.79-25.45Q571.44-25.45 573.39-24.35 575.34-23.25 576.64-21.38 577.94-19.5 578.57-17.23 579.19-14.95 579.19-12.6L579.19-12.6Q579.19-9.15 577.97-6.13 576.74-3.1 574.24-1.25 571.74 0.6 567.94 0.6L567.94 0.6Q566.34 0.6 564.66 0.1 562.99-0.4 561.79-1.5L561.79-1.5Q561.89-0.8 561.89-0.08 561.89 0.65 561.89 1.1L561.89 1.1 561.89 7.9 562.59 10.8 557.39 10.8 557.39-17.65Q557.39-18.7 557.24-19.98 557.09-21.25 556.76-22.5 556.44-23.75 555.84-24.65L555.84-24.65ZM567.44-21.6L567.44-21.6Q565.84-21.6 564.37-20.65 562.89-19.7 561.89-18.45L561.89-18.45 561.89-10.1Q561.89-8.25 562.49-6.6 563.09-4.95 564.37-3.95 565.64-2.95 567.74-2.95L567.74-2.95Q569.64-2.95 570.91-3.8 572.19-4.65 572.97-6.05 573.74-7.45 574.04-9.08 574.34-10.7 574.34-12.3L574.34-12.3Q574.34-13.95 573.99-15.6 573.64-17.25 572.82-18.6 571.99-19.95 570.69-20.78 569.39-21.6 567.44-21.6ZM584.64-38.15L589.84-38.15 589.84 0 585.29 0 585.29-35.3 584.64-38.15ZM607.28-25.45L607.28-25.45Q610.38-25.45 612.46-24.18 614.53-22.9 615.58-20.68 616.63-18.45 616.63-15.6L616.63-15.6Q616.63-14.9 616.56-14.15 616.48-13.4 616.33-12.55L616.33-12.55 600.63-12.55Q600.63-8 602.71-5.5 604.78-3 608.18-3L608.18-3Q609.63-3 611.23-3.45 612.83-3.9 614.53-4.85L614.53-4.85 616.08-2.15Q614.38-0.75 612.08-0.05 609.78 0.65 607.58 0.65L607.58 0.65Q603.68 0.65 601.06-1.15 598.43-2.95 597.13-5.9 595.83-8.85 595.83-12.15L595.83-12.15Q595.83-14.7 596.56-17.05 597.28-19.4 598.73-21.33 600.18-23.25 602.31-24.35 604.43-25.45 607.28-25.45ZM606.93-22.1L606.93-22.1Q605.23-22.1 603.96-21.18 602.68-20.25 601.93-18.8 601.18-17.35 600.93-15.85L600.93-15.85 611.88-15.85Q611.98-17.8 611.38-19.2 610.78-20.6 609.66-21.35 608.53-22.1 606.93-22.1Z"
opacity="1" transform="matrix(1,0,0,1,0,0)"
fill="#0074d9" class="undefined-text-1"
data-fill-palette-color="secondary"
id="text-1"></path>
</svg>
</g>
</svg>
</g>
</svg>
</g>
</svg>
</g>
<g transform="matrix(1,0,0,1,445.74250728117676,0)">
<svg viewBox="0 0 197.64500178114181 178.55006903019566"
height="178.55006903019566" width="197.64500178114181">
<g>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0"
y="0"
viewBox="11.999974250793457 15.855531692504883 75.0999526977539 67.84437561035156"
enable-background="new 0 0 100 100" xml:space="preserve"
height="178.55006903019566" width="197.64500178114181"
class="icon-icon-0" data-fill-palette-color="accent" id="icon-0"><path fill-rule="evenodd" clip-rule="evenodd" d="M52.6 29.6c0 0.6 0.1 1.1 0.2 1.6 0.1 0.5 0 1 0.1 1.6 0 0.2 0.1 0.5 0.2 0.7 0.1 0.4 0.1 0.7 0.2 1.1 0.1 0.2 0.1 0.4 0.2 0.7 0.1 0.2 0 0.4 0.1 0.8 0 0.1 0.1 0.3 0.1 0.4 0 0.3-0.1 0.7-0.1 1 0 0.3 0.1 0.8 0.1 1.1 0 0.6 0 1.1 0 1.8 0 0.1 0.1 0.3 0.1 0.5 0 0.4-0.1 0.8-0.1 1 0.1 0.3 0.2 0.3 0.4 0.5 0 0 0 0.1 0.1 0.1 0.1 0 0.2 0.2 0.3 0.3 0 0 0.1 0 0.2 0 0.1 0.1 0.1 0.2 0.3 0.3 0.1 0.1 0.2 0.1 0.3 0.2 0.2 0.1 0.4 0.5 0.6 0.6 0.1 0 0.2 0 0.2 0.1 0 0 0 0.1 0.1 0.1 0.1 0 0.2 0.2 0.3 0.3 0.1 0.1 0.2 0 0.2 0.1 0.1 0.1 0.2 0.3 0.3 0.4 0.1 0.1 0.4 0.3 0.6 0.5 0 0 0 0.2 0.1 0.2 0 0 0.3 0.2 0.5 0.2 0.2 0 0.5 0 0.9-0.1 0 0 0-0.1 0.1-0.1 0.7 0 1.5-0.3 2.1-0.4 0.6-0.1 1.1-0.2 1.6-0.2 0 0 0-0.1 0.1-0.1 0.2 0 0.4 0 0.6-0.1 0.3 0 0.5 0 0.8-0.1 0 0 0-0.1 0.1-0.1 0.1 0 0.2 0 0.3 0 0 0 0-0.1 0.1-0.1 0.3 0 0.4 0 0.7-0.1 0.1 0 0.2 0 0.2 0 0.1 0 0.3-0.1 0.6-0.1 0.1 0 0.2 0 0.2 0 0.5-0.1 1.5-0.3 2.1-0.4 0.1 0 0.3 0 0.3 0 0 0 0.2-0.1 0.4-0.2 0.2-0.1 0.4-0.1 0.7-0.2 0.3-0.1 0.6-0.1 1-0.2 0.3 0 0.6 0 0.7-0.1 0.2-0.1 0.3-0.2 0.4-0.3 0.1-0.1 0.1-0.1 0.2-0.2 0-0.1 0.1-0.1 0.2-0.2 0.1-0.1 0-0.2 0.1-0.3 0 0 0.1-0.1 0.2-0.2 0.1-0.1 0.2-0.2 0.3-0.3 0 0 0-0.1 0-0.2 0 0 0.1-0.1 0.2-0.1 0.1-0.1 0-0.2 0.1-0.2 0.1-0.1 0.2-0.2 0.3-0.3 0 0 0-0.1 0-0.2 0.1-0.1 0.2-0.1 0.3-0.2 0.1-0.1 0.2-0.3 0.2-0.4 0 0 0.1 0 0.2 0 0.1-0.1 0.2-0.3 0.4-0.4 0.1-0.1 0.1-0.2 0.2-0.2 0 0 0.1 0 0.2 0 0.1-0.1 0.1-0.2 0.3-0.2 0 0 0.1 0 0.2 0 0.2-0.2 0.8-0.4 1.2-0.5 0.4-0.1 1.2 0 1.6-0.2 0.1-0.1 0.2-0.2 0.5-0.2 0.2-0.1 0.5-0.1 0.9-0.2 0.2 0 0.4-0.1 0.6-0.1 0.2 0 0.4 0 0.6 0.1 0 0 0 0.1 0.1 0.1 0.2 0 0.2 0.1 0.4 0.2 0.4 0.1 0.8 0.2 1 0.3 0 0 0 0.1 0.1 0.1 0 0 0.1 0.1 0.2 0.1 0.1 0 0.1 0.1 0.2 0.2 0 0 0.1 0 0.2 0 0 0 0.1 0.1 0.1 0.2 0.1 0.1 0.2 0 0.2 0.1 0 0 0 0.1 0.1 0.1 0.1 0 0.2 0.2 0.3 0.2 0 0 0.1 0.1 0.2 0.2 0.1 0 0.2 0.2 0.3 0.2 0.1 0 0.2 0.2 0.3 0.2 0.1 0.1 0.2 0.2 0.3 0.3 0 0 0.1 0 0.2 0 0 0 0.1 0.1 0.1 0.2 0.1 0.1 0.4 0.2 0.6 0.4 0 0.1 0 0.2 0.1 0.2 0.1 0.1 0.3 0.4 0.3 0.6 0 0.2 0.1 0.3 0.2 0.5 0 0 0 0.1 0 0.2 0.2 0.1 0.2 0.6 0.2 1 0.1 0.3 0.2 0.6 0.2 0.9 0 0.7-0.3 1.3-0.6 2-0.1 0.1 0 0.2-0.1 0.3 0 0-0.1 0.1-0.2 0.2-0.1 0.1-0.1 0.3-0.2 0.5 0 0.1 0 0.2-0.1 0.2 0 0-0.1 0.1-0.2 0.2-0.1 0.1-0.1 0.4-0.2 0.5-0.1 0.1-0.2 0.2-0.3 0.3-0.1 0.1-0.2 0.2-0.3 0.3-0.2 0.2-0.4 0.5-0.7 0.7 0 0-0.1 0.1-0.1 0.1-0.1 0.1-0.3 0.1-0.4 0.2 0 0-0.1 0.1-0.2 0.2-0.1 0.1-0.2 0-0.3 0.1-0.1 0-0.1 0.1-0.2 0.2-0.1 0-0.2 0-0.2 0.1 0 0-0.1 0.1-0.2 0.2 0 0-0.1 0-0.2 0-0.1 0-0.2 0.1-0.4 0.2-0.1 0-0.3 0-0.3 0 0 0 0 0.1 0 0.1-0.2 0-0.4 0.1-0.6 0.1-0.1 0-0.2 0.1-0.3 0.1-1.1 0.1-2.2-0.1-3.1-0.4-0.1 0-0.3-0.1-0.4-0.2-0.2-0.1-0.4-0.1-0.7-0.2 0 0 0-0.1-0.1-0.1 0 0-0.1 0-0.1 0-0.1 0-0.1-0.1-0.2-0.1-0.1 0-0.5-0.2-0.7-0.2 0 0 0-0.1-0.1-0.1 0 0-0.1-0.1-0.2-0.1-0.1 0-0.2-0.2-0.3-0.2-0.1 0-0.4-0.3-0.6-0.5-0.2-0.2-0.5-0.4-0.7-0.6-0.1-0.1-0.2-0.2-0.2-0.2 0 0 0-0.1 0-0.2-0.1-0.1-0.3-0.1-0.3-0.4-0.1 0-0.3-0.1-0.3-0.2-0.4 0-0.7 0.1-1 0.2-0.5 0.1-1.2 0-1.6 0.2-0.1 0.1-0.5 0.2-0.7 0.2-0.1 0-0.3 0-0.5 0-0.1 0-0.2 0.1-0.3 0.1-0.2 0-0.4-0.1-0.8-0.1-0.5 0-1 0.2-1.6 0.2-0.3 0-0.6 0-0.9 0.1-0.1 0-0.5 0.1-0.7 0.2-0.4 0.1-0.8 0.2-1.2 0.2-0.1 0-0.2 0-0.2 0-0.3 0.1-0.9 0.1-1.4 0.2-0.4 0.1-1.2 0-1.7 0.2-0.1 0-0.2 0-0.3 0.1-0.3 0.2-0.3 0.7-0.4 1.2-0.1 0.3-0.1 0.3-0.2 0.5 0 0.1-0.1 0.2-0.2 0.3-0.1 0.1-0.1 0.3-0.2 0.4 0 0.1-0.1 0.1-0.2 0.2-0.1 0.2-0.1 0.3-0.2 0.5 0 0-0.1 0-0.1 0.1 0 0.1-0.1 0.2-0.2 0.2-0.1 0.1-0.1 0.2-0.2 0.4 0 0-0.1 0-0.1 0.1 0 0.1-0.1 0.2-0.2 0.2-0.1 0.1-0.3 0.3-0.3 0.6 0 0.2 0.1 0.4 0.2 0.6 0.1 0.2 0.1 0.4 0.2 0.6 0.3 0.6 0.6 1 1 1.6 0 0.1 0 0.1 0.1 0.2 0 0 0.1 0.1 0.1 0.1 0.1 0.1 0 0.2 0.1 0.3 0 0.1 0.2 0.2 0.2 0.2 0 0 0 0.1 0 0.2 0 0 0.1 0.1 0.2 0.1 0.1 0.1 0 0.2 0.1 0.3 0 0 0.1 0.1 0.2 0.1 0.1 0.1 0 0.2 0.1 0.3 0 0 0.1 0 0.1 0.1 0 0.1 0.2 0.2 0.2 0.4 0 0.1 0.1 0.2 0.2 0.3 0.1 0.1 0.3 0.4 0.4 0.6 0 0 0.1 0.1 0.2 0.1 0.1 0.2 0.1 0.2 0.2 0.4 0.2 0.3 0.5 0.6 0.7 1 0 0.1 0.2 0.2 0.2 0.3 0 0.1 0.2 0.2 0.3 0.3 0 0 0 0.1 0 0.2 0 0 0.1 0.1 0.2 0.1 0.1 0.1 0 0.2 0.1 0.3 0 0 0.1 0.1 0.2 0.1 0.1 0.2 0.1 0.3 0.2 0.6 0.6 0.9 1.2 1.9 1.8 2.9 0.1 0.2 0.2 0.4 0.4 0.6 0 0 0.1 0.1 0.1 0.1 0.1 0.1 0 0.2 0.1 0.3 0 0 0.1 0.1 0.2 0.1 0 0 0 0.1 0 0.2 0 0 0.1 0.1 0.2 0.1 0.1 0.2 0.1 0.2 0.2 0.4 0 0.1 0.2 0.2 0.2 0.3 0 0.1 0.2 0.1 0.2 0.2 0 0.1 0.1 0.3 0.2 0.4 0 0.1 0.1 0.1 0.2 0.2 0 0 0 0.1 0 0.2 0 0 0.1 0.1 0.2 0.1 0 0 0 0.1 0 0.2 0 0 0.1 0 0.2 0.1 0 0 0 0.1 0 0.2 0.1 0.1 0.2 0.1 0.3 0.2 0.3 0.1 0.6 0 1 0 0.2 0 0.3 0.1 0.5 0.1 0.4 0.1 0.7 0.1 1.2 0 0 0 0.1 0 0.2 0 0 0 0-0.1 0-0.1 0.6-0.1 1.3 0 1.8 0.2 0 0 0.1 0 0.2 0 0.1 0.1 0.5 0.2 0.7 0.2 0 0 0.1 0 0.2 0 0 0 0 0.1 0 0.1 0 0 0.1 0 0.2 0 0 0 0 0.1 0 0.1 0 0 0.1 0 0.2 0 0 0 0 0.1 0 0.1 0 0 0.1 0 0.2 0 0 0 0.1 0.1 0.1 0.2 0.1 0.1 0.3 0.2 0.5 0.3 0.1 0.1 0.1 0.3 0.3 0.3 0.1 0 0.2 0.1 0.3 0.2 0.1 0.1 0.1 0.1 0.2 0.2 0.1 0.1 0.2 0.2 0.3 0.2 0.2 0 0.7 0.7 0.9 0.9 0 0 0.1 0 0.2 0 0 0 0 0.1 0.1 0.2 0 0 0.2 0.1 0.2 0.1 0 0 0 0.1 0 0.2 0 0.1 0.2 0.1 0.2 0.2 0 0.1 0 0.2 0.1 0.2 0 0.1 0.1 0.1 0.1 0.2 0.1 0.2 0.1 0.5 0.2 0.7 0.2 0.5 0.7 1.2 0.7 1.8 0.1 0.5 0 1-0.2 1.5 0 0.1-0.1 0.3-0.2 0.5 0 0.1-0.1 0.2-0.2 0.4 0 0.1 0 0.2-0.1 0.3 0 0-0.2 0.1-0.2 0.2 0 0.1 0 0.1-0.1 0.2-0.1 0.1-0.3 0.4-0.4 0.6-0.1 0.1-0.3 0.2-0.4 0.4-0.1 0.1-0.2 0.4-0.3 0.5-0.1 0.1-0.2 0-0.2 0.1 0 0 0 0.1-0.1 0.2-0.1 0-0.2 0-0.2 0.1 0 0 0 0.1-0.1 0.2-0.1 0.1-0.4 0.2-0.6 0.2 0 0 0 0.1-0.1 0.1-0.1 0-0.1 0.1-0.2 0.2 0 0-0.1 0-0.2 0-0.2 0.1-0.3 0.5-0.5 0.6-0.1 0.1-0.3 0.2-0.5 0.3C73 82.9 72.8 83 72.7 83c-0.3 0.1-0.5 0.1-0.9 0.1-0.9 0.1-2 0.1-3.1-0.1-0.1 0-0.1-0.1-0.2-0.1-0.2 0-0.3 0-0.6-0.1 0 0-0.1 0-0.2 0 0 0 0-0.1 0-0.1-0.4-0.1-0.7 0-1-0.1 0 0 0-0.1-0.1-0.1-0.2 0-0.2-0.1-0.4-0.2-0.1 0-0.2 0-0.3-0.1-0.1 0-0.1-0.1-0.2-0.2-0.1 0-0.1 0-0.2-0.1-0.1 0-0.1-0.1-0.2-0.2 0 0-0.1 0-0.2 0 0 0-0.2-0.2-0.3-0.2 0 0-0.1 0-0.1 0-0.1 0-0.1-0.1-0.2-0.2 0 0-0.1 0-0.2 0-0.1-0.1-0.1-0.2-0.3-0.2-0.1 0-0.2-0.2-0.3-0.2-0.2-0.2-0.4-0.3-0.6-0.5-0.3-0.3-0.7-0.7-1-1.1-0.1-0.1-0.1-0.1-0.2-0.2 0 0 0-0.1-0.1-0.2C62 79.1 61.8 79 61.8 79c0 0 0-0.1 0-0.2 0 0-0.2-0.2-0.2-0.4 0 0-0.1-0.1-0.1-0.2-0.1-0.2-0.1-0.4-0.2-0.6 0 0-0.1 0-0.1-0.1 0-0.2 0-0.2-0.1-0.4-0.2-0.5-0.2-1.3 0-1.8 0 0 0-0.1 0-0.2 0.1-0.1 0.1-0.1 0.2-0.2 0 0 0-0.1 0-0.2 0 0 0.1 0 0.1 0 0.1-0.2 0-0.4 0.1-0.5 0-0.1 0.1-0.1 0.2-0.2 0-0.1 0-0.2 0.1-0.2 0-0.1 0.1-0.1 0.2-0.2 0 0 0-0.1 0-0.2 0.4-0.3 0.6-0.7 0.3-1.4 0 0-0.1 0-0.1-0.1 0-0.1-0.1-0.2-0.2-0.3 0 0-0.2-0.3-0.2-0.5 0-0.1-0.2-0.5-0.3-0.6 0 0-0.1 0-0.1-0.1 0-0.1-0.1-0.2-0.2-0.3-0.1-0.1-0.2-0.4-0.3-0.6 0 0-0.1 0-0.1-0.1 0-0.1-0.1-0.2-0.2-0.3-0.1-0.1-0.1-0.2-0.2-0.2-0.1-0.1-0.1-0.3-0.2-0.5-0.1-0.1-0.1-0.2-0.2-0.2C60 68.3 60 68.2 59.9 68c0 0-0.1-0.1-0.2-0.1-0.1-0.2-0.1-0.2-0.2-0.4 0 0-0.1 0-0.1-0.1 0-0.1-0.2-0.2-0.2-0.4 0-0.1 0-0.2-0.1-0.2 0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2 0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2 0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2 0-0.1-0.1-0.1-0.2-0.2-0.1-0.1-0.1-0.2-0.2-0.4 0 0-0.1-0.1-0.1-0.1-0.1-0.1 0-0.2-0.1-0.3 0-0.1-0.1-0.1-0.2-0.2 0-0.1 0-0.1-0.1-0.2 0 0-0.2-0.3-0.2-0.5 0-0.1-0.1-0.2-0.2-0.3-0.1-0.1-0.1-0.2-0.2-0.2 0 0-0.1-0.1-0.1-0.1-0.1-0.1 0-0.2-0.1-0.3 0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2 0-0.1-0.1-0.1-0.2-0.2 0 0 0-0.1 0-0.2 0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2 0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2-0.2-0.1-0.3-0.5-0.5-0.7-0.1-0.1-0.2-0.4-0.3-0.6 0 0-0.1 0-0.1-0.1 0-0.1-0.2-0.1-0.2-0.2 0-0.1-0.2-0.3-0.2-0.4 0-0.1 0-0.1-0.1-0.2 0 0-0.2-0.4-0.2-0.5 0-0.1-0.2-0.2-0.2-0.4 0-0.1-0.2-0.2-0.2-0.3 0-0.1-0.1-0.2-0.2-0.3 0 0-0.1-0.1-0.1-0.1-0.1-0.1 0-0.2-0.1-0.3 0-0.1-0.1-0.1-0.2-0.2C54 58 54 57.9 54 57.8c0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2-0.1-0.2-0.3-0.2-0.4-0.3-0.4 0.1-0.8 0.1-1.3 0.2 0 0 0 0.1-0.1 0.1-0.5 0-1.2 0.1-2 0 0 0-0.1 0-0.2 0-0.1-0.1-0.4 0-0.5-0.1-0.1-0.2-0.2 0-0.5-0.1-0.5-0.1-1.4-0.3-1.9-0.6 0 0 0-0.1-0.1-0.1-0.1 0-0.2-0.1-0.4-0.2-0.1 0-0.2 0-0.3-0.1-0.1 0-0.1-0.1-0.2-0.2 0 0-0.1 0-0.2 0 0 0 0-0.1 0-0.1-0.1 0-0.2 0-0.2-0.1 0 0-0.1-0.1-0.2-0.2 0 0-0.1 0-0.2 0 0 0-0.2-0.1-0.2-0.1-0.1-0.1-0.6-0.3-0.8-0.4-0.1 0-0.1-0.1-0.2-0.2-0.2-0.1-0.5-0.4-0.7-0.6-0.1-0.1-0.3-0.3-0.4-0.5 0 0-0.1 0-0.1-0.1 0-0.1-0.1-0.2-0.2-0.2-0.1-0.1-0.1-0.2-0.2-0.3-0.1-0.1-0.2-0.3-0.4-0.4-0.3 0-0.5 0.3-0.8 0.4-0.1 0-0.2 0.2-0.3 0.2-0.1 0-0.2 0.1-0.3 0.2-0.3 0.2-0.9 0.5-1.3 0.8-0.1 0-0.1 0.1-0.2 0.2-0.6 0.4-1.2 0.7-1.8 1.1 0 0 0 0.1-0.1 0.1 0 0-0.1 0.1-0.2 0.1-0.1 0-0.1 0.1-0.2 0.2-0.1 0-0.1 0-0.2 0.1 0 0-0.1 0.1-0.1 0.1-0.1 0-0.2 0-0.2 0.1 0 0-0.1 0.1-0.1 0.2 0 0-0.1 0-0.2 0 0 0-0.1 0.1-0.1 0.2 0 0-0.1 0-0.2 0-0.1 0-0.1 0.2-0.2 0.2-0.1 0.1-0.2 0-0.2 0.1 0 0-0.1 0.1-0.1 0.2-0.1 0-0.2 0.1-0.3 0.2-0.1 0.1-0.3 0.2-0.4 0.3-0.1 0.1-0.3 0.3-0.4 0.4-0.1 0.1-0.3 0.2-0.4 0.3-0.1 0.1-0.2 0.1-0.2 0.2 0 0-0.1 0.1-0.1 0.2 0 0-0.1 0-0.2 0-0.1 0.1-0.2 0.3-0.3 0.4-0.6 0.5-1.3 0.9-1.9 1.4 0 0 0 0.1-0.1 0.1-0.1 0-0.1 0.2-0.2 0.2-0.1 0-0.2 0.2-0.3 0.2-0.1 0.1-0.2 0.1-0.2 0.2-0.1 0-0.1 0.1-0.2 0.2 0 0-0.1 0-0.2 0 0 0-0.1 0.1-0.1 0.2-0.1 0.1-0.2 0-0.2 0.1 0 0-0.1 0.1-0.1 0.2 0 0-0.1 0-0.2 0-0.1 0.1-0.1 0.2-0.2 0.2 0 0-0.1 0-0.2 0-0.2 0.2-0.4 0.4-0.6 0.6-0.1 0.1-0.2 0.1-0.2 0.2 0 0.1 0.1 0.2-0.1 0.2 0.2 0.4 0.3 0.8 0.4 1 0 0 0.1 0 0.1 0.1 0 0.2 0.2 0.4 0.2 0.6 0 0 0 0.1 0 0.2 0 0 0.1 0.1 0.1 0.1 0.1 0.3 0 0.8 0 1.1 0 0.7 0 1.1-0.2 1.5 0 0.1 0 0.1-0.1 0.2 0 0 0 0.1 0 0.2-0.1 0.1-0.1 0.1-0.1 0.2 0 0-0.1 0.2-0.1 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2-0.1 0.1-0.3 0.5-0.4 0.8 0 0.1-0.1 0.3-0.2 0.4 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2 0 0-0.2 0.2-0.2 0.3 0 0 0 0.1 0 0.1-0.1 0.1-0.2 0.2-0.2 0.2 0 0 0 0.1 0 0.2-0.1 0.1-0.1 0.1-0.2 0.2-0.1 0.1-0.2 0.1-0.2 0.2-0.1 0.2-0.3 0.4-0.4 0.6-0.1 0.1-0.2 0.1-0.2 0.2 0 0 0 0.1 0 0.2-0.2 0.1-0.6 0.4-0.9 0.6-0.1 0-0.1 0.1-0.2 0.2-0.1 0.1-0.2 0-0.2 0.1 0 0-0.1 0.1-0.2 0.2 0 0-0.1 0-0.2 0 0 0 0 0.1 0 0.1 0 0-0.1 0-0.2 0-0.2 0.2-0.9 0.3-1.4 0.4-0.1 0-0.2 0-0.3 0-0.3 0.1-1 0.1-1.6 0.1-0.3 0-0.5 0.1-0.7 0-0.1 0-0.3 0-0.5 0-0.3 0-0.7-0.2-1-0.2-0.2 0-0.4 0-0.6-0.1-0.2 0-0.2 0-0.4-0.1-0.2 0-0.7-0.3-0.9-0.5-0.1-0.1-0.2-0.1-0.3-0.2 0 0-0.1-0.1-0.1-0.1-0.1 0-0.2-0.2-0.3-0.2 0 0-0.1-0.1-0.2-0.1-0.1 0-0.2 0-0.2-0.1 0 0-0.1-0.1-0.1-0.2 0 0-0.1 0-0.2 0 0 0-0.1-0.1-0.1-0.2 0 0-0.1 0-0.2 0 0 0-0.1-0.1-0.1-0.2-0.1 0-0.2-0.1-0.3-0.2-0.1-0.1-0.3-0.2-0.4-0.3 0 0-0.1-0.1-0.1-0.2-0.1 0-0.2 0-0.3-0.2-0.1-0.1 0-0.2-0.1-0.2 0 0-0.1 0-0.2-0.1-0.1-0.1-0.1-0.2-0.2-0.3 0 0-0.1 0-0.1-0.1 0-0.1-0.1-0.2-0.2-0.3 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0-0.1-0.2-0.2-0.4-0.2-0.6 0 0-0.1 0-0.1-0.1 0-0.1-0.2-0.2-0.2-0.3 0-0.1-0.1-0.3-0.2-0.4 0-0.2 0.1-0.6 0.2-1 0-0.3 0-0.6 0.1-1 0 0 0.1 0 0.1-0.1 0-0.1 0-0.3 0-0.5 0 0 0.1 0 0.1-0.1 0-0.2 0-0.2 0.1-0.3 0-0.1 0-0.2 0.1-0.2 0-0.1 0.1-0.2 0.2-0.3 0.1-0.4 0.1-0.8 0.2-1.1 0 0 0.1 0 0.1-0.1 0-0.1 0.1-0.3 0.2-0.4 0.1-0.2 0.3-0.2 0.4-0.4 0.2-0.2 0.3-0.5 0.5-0.7 0-0.1 0.1-0.1 0.2-0.2 0 0 0-0.1 0-0.2 0 0 0.1-0.1 0.2-0.1 0 0 0-0.1 0-0.2 0 0 0.1-0.1 0.2-0.1 0 0 0-0.1 0-0.2 0 0 0.1-0.1 0.2-0.1 0.1-0.1 0-0.2 0.1-0.2 0 0 0.1-0.1 0.2-0.1 0 0 0-0.1 0-0.2 0.1-0.1 0.2-0.1 0.2-0.2 0.2-0.2 0.3-0.3 0.5-0.5 0 0 0-0.1 0.1-0.1 0 0 0.1 0 0.1 0 0.1-0.1 0.2-0.2 0.2-0.2 0.1-0.1 0.2-0.1 0.3-0.2 0 0 0-0.1 0.1-0.1 0.1 0 0.2-0.2 0.3-0.2 0 0 0.2-0.1 0.2-0.1 0.1 0 0.2-0.2 0.2-0.2 0.1 0 0.3 0 0.4-0.1 0 0 0-0.1 0.1-0.1 0 0 0.1 0 0.1 0 0 0 0.1-0.1 0.1-0.1 0.3-0.1 0.6 0 1-0.1 0 0 0-0.1 0.1-0.1 0.4 0 1.2-0.1 1.7-0.2 0.3 0 0.7 0 1 0 0.3 0 0.7 0.2 1 0.4 0.1 0 0.2 0 0.3 0.1 0 0 0.1 0.1 0.2 0.2 0.3 0.1 0.6 0.2 1 0.1 0.1-0.1 0.1-0.1 0.2-0.2 0.2-0.1 0.3-0.2 0.6-0.4 0.1-0.1 0.1-0.2 0.2-0.2 0.1 0 0.2-0.2 0.2-0.2 0.1 0 0.2-0.2 0.3-0.2 0.4-0.3 1-0.7 1.4-1 0.1-0.1 0.2-0.2 0.2-0.2 0 0 0.1 0 0.2 0 0.1-0.1 0.1-0.1 0.2-0.2 0.1-0.1 0.2-0.2 0.2-0.2 0 0 0.1 0 0.2 0 0.3-0.3 0.8-0.7 1.1-1 0.2-0.2 0.4-0.5 0.7-0.7 0.2-0.2 0.5-0.4 0.7-0.6 0.1-0.1 0.1-0.2 0.2-0.2 0.1-0.1 0.2-0.1 0.3-0.2 0.1-0.1 0.1-0.2 0.2-0.2 0.1 0 0.2-0.2 0.2-0.2 0.1 0 0.4-0.3 0.6-0.4 0.1 0 0.2-0.2 0.2-0.2 0 0 0.1 0 0.1 0 0.1-0.1 0.1-0.2 0.2-0.2 0.1-0.1 0.3-0.1 0.5-0.2 0 0 0-0.1 0.1-0.1 0 0 0.1 0 0.1 0 0.1-0.1 0.1-0.2 0.2-0.2 0.1-0.1 0.2-0.1 0.3-0.2 0.1-0.1 0.2-0.2 0.2-0.2 0 0 0.1 0 0.2 0 0 0 0.1-0.1 0.1-0.2 0.1-0.1 0.2-0.1 0.3-0.2 0.1-0.1 0.1-0.2 0.2-0.2 0 0 0.1 0 0.2 0 0 0 0-0.1 0-0.1 0 0 0.1 0 0.2 0 0.1-0.1 0.1-0.2 0.2-0.2 0.1 0 0.2-0.2 0.3-0.2 0.1 0 0.2-0.2 0.2-0.2 0 0 0.1 0 0.1 0 0.1-0.1 0.2-0.2 0.2-0.2 0 0 0.1 0 0.2 0 0 0 0.1-0.1 0.1-0.2 0 0 0.1 0 0.2 0 0 0 0-0.1 0-0.1 0 0 0.1 0 0.2 0 0 0 0-0.1 0-0.1 0.1-0.1 0.3-0.1 0.5-0.2 0 0 0-0.1 0.1-0.1 0 0 0.1 0 0.1 0 0.1-0.1 0.1-0.2 0.2-0.2 0.5-0.4 1.1-0.7 1.7-1.1 0.1-0.1 0.1-0.2 0.2-0.2 0 0 0.1 0 0.2 0 0 0 0.1-0.1 0.1-0.2 0.1-0.1 0.3 0 0.4-0.1 0 0 0-0.1 0.1-0.1 0.2 0 0.3-0.2 0.5-0.3 0.1-0.1 0.2-0.1 0.2-0.2 0.1-0.2 0.2-0.4 0.2-0.6 0.1-0.4 0.3-0.7 0.6-1.1 0-0.1 0-0.1 0.1-0.2 0 0 0.1-0.1 0.1-0.1 0.1-0.1 0-0.2 0.1-0.3 0-0.1 0.2-0.1 0.2-0.2 0 0 0-0.1 0-0.2 0 0 0.1 0 0.2-0.1 0.1-0.2 0.2-0.3 0.3-0.5 0.1-0.1 0.3-0.3 0.4-0.4 0.2-0.2 0.3-0.3 0.6-0.5 0 0 0-0.1 0.1-0.1 0 0 0.1 0 0.1 0 0.1-0.1 0.1-0.2 0.2-0.2 0 0 0.1 0 0.2 0 0.1-0.1 0.2-0.2 0.4-0.3 0.5-0.3 0.9-0.6 1.6-0.7 0 0 0.1-0.1 0.1-0.1 0.2 0 0.4 0 0.6 0 0.4-0.1 0.7-0.2 0.8-0.4 0.1-0.2 0.1-0.2 0.2-0.6 0-0.4 0-0.7-0.1-1 0-0.1 0-0.2 0-0.3 0 0-0.1 0-0.1-0.1 0-0.1 0-0.3 0-0.4-0.1-0.3-0.1-1-0.2-1.5 0 0-0.1 0-0.1-0.1 0-0.4 0-0.7-0.1-1.2-0.1-0.4-0.2-0.8-0.3-1.2-0.1-0.5-0.2-1.1-0.3-1.6 0-0.1 0-0.2 0-0.2 0-0.1-0.1-0.2-0.1-0.5 0 0 0-0.1 0-0.2 0-0.1-0.1-0.5-0.2-0.8 0 0-0.1 0-0.1-0.1 0-0.1 0-0.2 0-0.2 0-0.2-0.1-0.9-0.2-1.3 0-0.4 0-0.6-0.2-0.9 0-0.1-0.1-0.2-0.2-0.3 0 0-0.1 0-0.2 0 0 0 0-0.1 0-0.1 0 0-0.1 0-0.2 0 0 0 0-0.1 0-0.1 0 0-0.1 0-0.2 0 0 0 0-0.1 0-0.1 0 0-0.1 0-0.2 0 0 0 0-0.1 0-0.1 0 0-0.1 0-0.2 0 0 0 0-0.1 0-0.1 0 0-0.1 0-0.2 0-0.1-0.1-0.6-0.2-0.8-0.3-0.2-0.1-0.5-0.2-0.6-0.3 0 0 0-0.1-0.1-0.1 0 0-0.5-0.4-0.6-0.5 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2-0.2-0.2-0.5-0.4-0.7-0.7-0.1-0.2-0.1-0.3-0.2-0.6-0.1-0.4-0.5-1.2-0.6-1.8 0-0.4 0-0.9 0.1-1.2 0-0.1 0.1-0.2 0.2-0.3 0.1-0.2 0.1-0.5 0.2-0.7 0 0 0.1 0 0.1-0.1 0-0.1 0.1-0.2 0.2-0.3 0 0 0-0.1 0-0.2 0 0 0.1 0 0.1 0 0 0 0-0.1 0-0.2 0 0 0.1 0 0.1 0 0 0 0-0.1 0-0.2 0.1-0.1 0.1-0.1 0.2-0.2 0.2-0.3 0.4-0.9 0.9-1 0.1-0.3 0.3-0.5 0.4-0.7 0.2 0 0.4-0.2 0.6-0.3 0 0 0.1-0.1 0.1-0.2 0 0 0.1 0 0.2 0 0 0 0.1-0.1 0.1-0.2 0 0 0.1 0 0.2 0 0 0 0-0.1 0.1-0.2 0 0 0.1 0 0.2 0 0 0 0-0.1 0-0.1 0.1-0.1 0.2 0 0.3-0.1 0.1-0.1 0.2-0.1 0.4-0.2 0.2-0.1 0.7-0.2 1-0.3 0 0 0-0.1 0.1-0.1 0.1 0 0.2 0 0.3-0.1 0.1 0 0.1-0.1 0.2-0.1 0.1 0 0.5-0.1 0.7-0.2 0.2 0 0.4-0.1 0.9 0 0 0 0 0.1 0.1 0.1 0.3 0.1 0.7 0 1.1 0.1 0.4 0 0.6 0.1 1 0.2 0.2 0.1 0.4 0.1 0.6 0.2 0.1 0 0.2 0.1 0.3 0.2 0.1 0 0.2 0 0.2 0.1 0 0 0.1 0.1 0.2 0.2 0.1 0.1 0.4 0.1 0.5 0.2 0.1 0 0.1 0.1 0.2 0.2 0.1 0 0.2 0 0.2 0.1 0.1 0 0.1 0.1 0.2 0.2 0.1 0.1 0.2 0.3 0.4 0.4 0.1 0.1 0.2 0 0.2 0.1 0 0 0 0.1 0.1 0.2 0.2 0.1 0.3 0.2 0.5 0.5 0 0.1 0 0.1 0.1 0.2 0.1 0.1 0.1 0.2 0.2 0.2 0 0 0.1 0.1 0.1 0.1 0 0.1 0 0.2 0.1 0.2 0 0.1 0.2 0.1 0.2 0.2 0 0 0 0.1 0 0.2 0.1 0.1 0.1 0.1 0.2 0.2 0 0.1 0.1 0.1 0.1 0.2 0 0.1 0.1 0.5 0.2 0.9 0 0.3 0 0.5 0 0.8 0 0.5 0.1 1.1 0.1 1.5 0 0.5-0.3 1-0.4 1.5 0 0.1-0.1 0.3-0.1 0.4-0.1 0.3-0.1 0.5-0.2 0.7 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2 0 0-0.1 0-0.2 0.1 0 0-0.1 0.2-0.1 0.2-0.2 0.1-0.2 0.1-0.4 0.2-0.1 0-0.1 0.1-0.2 0.2C55.2 27.9 55.1 28 55 28c0 0-0.1 0.1-0.2 0.2-0.1 0.1-0.2 0-0.3 0.1-0.1 0-0.1 0.1-0.2 0.2-0.1 0-0.1 0-0.2 0.1-0.1 0.1-0.1 0.1-0.2 0.2-0.1 0.1-0.4 0.2-0.6 0.3 0 0 0 0.1-0.1 0.1-0.1 0-0.2 0.1-0.3 0.2 0 0-0.1 0-0.2 0C52.8 29.4 52.8 29.5 52.6 29.6zM49.1 21c0 0-0.1 0-0.2 0 0 0-0.1 0.1-0.2 0.2-0.3 0.1-0.3 0.2-0.5 0.4-0.2 0.3-0.4 0.7-0.6 0.9-0.1 0.2-0.2 0.5-0.3 0.6 0 0 0 0.1 0 0.2 0 0.1-0.1 0.1-0.2 0.2-0.2 0.6-0.3 0.9-0.2 1.4 0.1 0.3 0.2 0.5 0.3 0.6 0.4 0.4 1.7 0.2 2.1 0.2 0.4-0.1 0.8-0.2 1-0.3 0.2-0.1 0.5-0.2 0.6-0.2 0.1 0 0.1 0 0.2-0.1 0.2-0.1 0.2-0.2 0.3-0.5 0 0 0.1 0 0.1 0 0 0 0-0.1 0-0.2 0.1-0.4 0.2-1 0.2-1.4 0.1-0.6 0-0.9-0.2-1.3C50.9 21.1 50.1 21.2 49.1 21zM78.9 42.3c0 0-0.1 0.1-0.2 0.2-0.2 0.1-0.4 0.2-0.5 0.3-0.1 0.1-0.1 0.2-0.2 0.3-0.1 0.1-0.2 0.2-0.2 0.3 0 0.1-0.1 0.2-0.2 0.2-0.1 0.1-0.2 0.1-0.2 0.2-0.1 0.1-0.1 0.1-0.2 0.2 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0-0.1 0.1 0 1.2 0.1 1.4 0.1 0.3 0.2 0.5 0.3 0.6 0.1 0.1 0.1 0.2 0.2 0.2 0.1 0 0 0.1 0.1 0.1 0.1 0.1 0.5 0.2 0.6 0.2 0.4 0 1-0.2 1.6-0.6 0.2-0.1 0.6-0.4 0.8-0.6 0.2-0.1 0.4-0.3 0.5-0.4 0.1-0.1 0-0.2 0.1-0.3 0 0 0.1-0.1 0.1-0.1 0 0 0.1-0.1 0.1-0.2 0.2-0.6 0.1-0.8 0-1.4-0.1-0.3 0-0.3-0.1-0.4 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0 0-0.1-0.1-0.2-0.2-0.1-0.1-0.3-0.2-0.4-0.2 0 0-0.1-0.1-0.2-0.1-0.1 0 0-0.1-0.1-0.1-0.3-0.1-1-0.2-1.3-0.2-0.1 0-0.1 0.1-0.1 0.1C79.1 42.3 78.9 42.2 78.9 42.3zM49.6 45.5c0 0-0.1 0.1-0.2 0.2-0.2 0.1-0.4 0.2-0.6 0.4-0.3 0.3-0.4 0.4-0.6 0.7-0.1 0.1-0.2 0.3-0.2 0.4 0 0.1 0 0.1-0.1 0.2 0 0-0.1 0.1-0.2 0.2 0 0.1 0 0.1-0.1 0.2 0 0-0.1 0.1-0.2 0.2 0 0.1-0.1 0.3-0.1 0.3-0.1 0.3-0.2 0.6-0.2 0.7 0 0.1-0.1 0.2-0.1 0.3-0.1 0.3 0 0.4-0.1 0.6-0.1 0.9 0 1.2 0.4 1.8 0 0.1 0 0.2 0.1 0.2 0 0.1 0.2 0.1 0.2 0.2 0.1 0.1 0 0.2 0.1 0.2 0.1 0.1 0.2 0.1 0.2 0.2 0.1 0.1 0.2 0.3 0.3 0.4 0.1 0 0.2 0 0.2 0.1 0 0 0.1 0.1 0.1 0.2 0 0 0.1 0 0.2 0 0 0 0.1 0.1 0.2 0.2 0.6 0.5 1.6 0.3 2.3 0.1 0.1 0 0.1-0.1 0.1-0.1 0.2-0.1 0.4-0.1 0.6-0.2 0 0 0-0.1 0.1-0.2 0.1-0.1 0.2 0 0.2-0.1 0.1-0.1 0.2-0.2 0.3-0.3 0.1-0.1 0.2-0.2 0.2-0.3 0.1-0.1 0.3-0.3 0.3-0.4 0-0.1 0.1 0 0.1-0.1 0.1-0.2 0.2-0.3 0.2-0.4 0.1-0.2 0.2-0.3 0.2-0.4 0 0 0.1-0.1 0.2-0.2 0.1-0.1 0.1-0.4 0.2-0.5 0-0.2 0-0.4 0.1-0.6 0.1-0.3 0-0.5 0-0.6-0.1-0.3 0-0.4-0.2-0.5 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0-0.1-0.2-0.2-0.2-0.2-0.1-0.1-0.3-0.3-0.4-0.4 0 0-0.1 0-0.2 0-0.2-0.2-0.3-0.3-0.5-0.5-0.4-0.4-1-1-1.3-1.4 0 0-0.1 0-0.2 0-0.1-0.1-0.2-0.2-0.2-0.2-0.1-0.1-0.2 0-0.3-0.2-0.1 0-0.2 0-0.2 0C50 45.2 49.8 45.3 49.6 45.5zM18.2 68c0 0 0.1 0.1 0.2 0.2 0 0.1 0 0.2 0.1 0.2 0.1 0.1 0.1 0.3 0.2 0.4 0.1 0.1 0.3 0.4 0.3 0.4 0.1 0 0 0.1 0.1 0.1 0.1 0 0.2 0 0.2 0.1 0.1 0.1 0.2 0.2 0.3 0.3 0.1 0.1 0.1 0.1 0.2 0.2 0.1 0.1 0.3 0.1 0.3 0.2 0.2 0.1 0.5 0.1 0.7 0.2 0.3 0 0.6 0.1 0.8-0.1 0 0 0.1 0 0.2 0 0.3-0.1 0.5-0.2 0.6-0.4 0 0 0.1 0 0.2 0 0.1-0.1 0.5-0.5 0.5-0.6 0-0.1 0.2-0.2 0.2-0.2 0 0 0-0.1 0-0.2 0-0.1 0.1-0.1 0.2-0.2 0.1-0.1 0.1-0.1 0.2-0.2 0 0 0-0.1 0-0.2 0 0 0.1 0 0.1 0 0 0 0-0.1 0-0.2 0 0 0.1 0 0.1 0 0 0 0-0.1 0-0.2 0 0 0.1 0 0.1 0 0 0 0-0.1 0-0.2 0 0 0.1 0 0.1 0 0 0 0-0.1 0-0.2 0.1-0.2 0.1-0.3 0.2-0.4 0-0.2 0.1-0.7 0-1.1 0-0.1-0.3-0.9-0.4-1 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0 0-0.1-0.1-0.2-0.1 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0 0-0.1 0-0.1 0 0 0 0-0.1 0-0.2 0-0.1-0.2-0.1-0.2-0.2-0.1-0.1-0.1-0.3-0.2-0.3-0.1-0.1-0.3-0.2-0.4-0.3-0.1-0.1-0.2-0.3-0.3-0.4 0 0-0.2 0-0.2 0 0 0-0.1-0.1-0.2-0.2-0.1-0.1-0.2-0.1-0.3-0.2-0.3-0.1-0.4 0.1-0.5 0.2-0.3 0.2-0.6 0.4-0.8 0.6-0.2 0.2-0.3 0.4-0.4 0.6-0.1 0.1-0.2 0.2-0.2 0.2 0 0 0 0.1 0 0.1 0 0.1-0.1 0-0.1 0.1-0.2 0.3-0.3 0.4-0.5 0.6 0 0 0 0.1 0 0.2 0 0-0.1 0-0.1 0 0 0 0 0.1 0 0.2 0 0.1-0.2 0.1-0.2 0.2 0 0.1-0.1 0-0.1 0.1-0.1 0.2-0.2 0.3-0.2 0.4-0.1 0.1-0.1 0.3-0.2 0.5-0.1 0.4-0.1 0.8-0.2 1.2C18.1 67.7 18.1 67.9 18.2 68zM71.1 74.8c-0.1-0.1-0.2-0.2-0.3-0.3-0.1-0.1-0.1 0-0.2-0.1 0 0-0.1-0.1-0.2-0.2-0.1-0.1-0.3-0.1-0.3-0.2-0.1-0.1-0.4-0.1-0.7-0.2-0.2 0-0.4-0.1-0.6-0.1-0.1 0-0.2 0.1-0.2 0.1-0.1 0 0 0.1-0.1 0.1-0.1 0-0.2 0-0.4 0.1 0 0 0 0.1 0 0.1 0 0-0.1 0-0.2 0 0 0-0.1 0.1-0.2 0.2-0.1 0.1-0.2 0.2-0.3 0.3-0.6 0.6-1.4 1.6-1.4 2.3 0 0.3 0.1 0.6 0.3 0.9 0 0 0.1 0.1 0.2 0.2 0 0.1 0 0.2 0.1 0.2 0.2 0.2 0.4 0.3 0.6 0.4 0 0 0 0.1 0 0.1 0 0 0.1 0 0.2 0 0.3 0.1 0.6 0.2 0.9 0.2 0.6 0.1 1.3 0.2 1.7 0.2C70 79 70 79 70.1 79c0.1 0 0-0.1 0.1-0.1 0.1-0.1 0.2-0.1 0.3-0.2 0.1-0.1 0.1-0.2 0.2-0.2 0 0 0.1 0 0.1 0 0.1 0 0-0.1 0.1-0.1 0.3-0.2 0.5-0.3 0.7-0.5 0.1-0.1 0.4-0.3 0.4-0.4 0-0.1 0.1-0.3 0.2-0.5 0.1-0.4 0.1-0.6 0-0.9 0-0.2-0.1-0.3-0.2-0.5 0 0-0.1-0.1-0.2-0.1 0-0.1 0-0.2-0.1-0.2 0 0-0.1-0.1-0.2-0.2 0-0.1 0-0.2-0.1-0.2C71.4 74.9 71.2 74.8 71.1 74.8z" fill="#0074d9" data-fill-palette-color="accent"></path></svg>
</g>
</svg>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="1440" height="560" preserveAspectRatio="none" viewBox="0 0 1440 560">
<g mask="url(&quot;#SvgjsMask1067&quot;)" fill="none">
<path d="M571.4372912881414 247.75240424047385L609.0971139583829 166.9906538989782 469.12457711101865 150.8817950643638z" fill="rgba(28, 83, 142, 0.4)" class="triangle-float2"></path>
<path d="M1015.653821794243 182.9470562446612L937.9708376138001 53.38132251166154 871.9416853095261 167.74716908336777z" fill="rgba(28, 83, 142, 0.4)" class="triangle-float2"></path>
<path d="M1004.02 303.09 a152.63 152.63 0 1 0 305.26 0 a152.63 152.63 0 1 0 -305.26 0z" fill="rgba(28, 83, 142, 0.4)" class="triangle-float1"></path>
<path d="M1097.0398993256729 385.43701679648467L1023.7348366528266 469.7648449846831 1108.062664841025 543.0699076575295 1181.3677275138714 458.742079469331z" fill="rgba(28, 83, 142, 0.4)" class="triangle-float3"></path>
<path d="M496.59 304.31 a176.64 176.64 0 1 0 353.28 0 a176.64 176.64 0 1 0 -353.28 0z" fill="rgba(28, 83, 142, 0.4)" class="triangle-float2"></path>
<path d="M377.3146591641146 434.54003525169793L328.1591851791916 352.731588453376 246.35073838086967 401.887062438299 295.50621236579263 483.6955092366209z" fill="rgba(28, 83, 142, 0.4)" class="triangle-float1"></path>
<path d="M112.40359570237194 412.98738163135965L84.45146236495073 534.0613728171735 205.52545355076455 562.0135061545947 233.47758688818578 440.9395149687809z" fill="rgba(28, 83, 142, 0.4)" class="triangle-float1"></path>
</g>
<defs>
<mask id="SvgjsMask1067">
<rect width="1440" height="560" fill="#ffffff"></rect>
</mask>
<style>
@keyframes float1 {
0%{transform: translate(0, 0)}
50%{transform: translate(-10px, 0)}
100%{transform: translate(0, 0)}
}
.triangle-float1 {
animation: float1 5s infinite;
}
@keyframes float2 {
0%{transform: translate(0, 0)}
50%{transform: translate(-5px, -5px)}
100%{transform: translate(0, 0)}
}
.triangle-float2 {
animation: float2 4s infinite;
}
@keyframes float3 {
0%{transform: translate(0, 0)}
50%{transform: translate(0, -10px)}
100%{transform: translate(0, 0)}
}
.triangle-float3 {
animation: float3 6s infinite;
}
</style>
</defs>
</svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" fill="none" stroke="currentColor" viewBox="0 0 48 48" width="32" height="32">
<path fill="currentColor" stroke="none" d="M.056 24.618c0 10.454 6.7 19.344 16.038 22.608 1.259.32 1.067-.582 1.067-1.19v-4.148c-7.265.853-7.553-3.957-8.043-4.758-.987-1.686-3.312-2.112-2.62-2.912 1.654-.853 3.34.213 5.291 3.1 1.413 2.09 4.166 1.738 5.562 1.385a6.777 6.777 0 0 1 1.856-3.253C11.687 34.112 8.55 29.519 8.55 24.057c0-2.646.874-5.082 2.586-7.045-1.088-3.243.102-6.01.26-6.422 3.11-.282 6.337 2.225 6.587 2.421 1.766-.474 3.782-.73 6.038-.73 2.266 0 4.293.26 6.069.74.603-.458 3.6-2.608 6.49-2.345.155.41 1.317 3.12.294 6.315 1.734 1.968 2.62 4.422 2.62 7.077 0 5.472-3.158 10.07-10.699 11.397a6.82 6.82 0 0 1 2.037 4.875v6.02c.042.48 0 .96.806.96 9.477-3.194 16.299-12.15 16.299-22.697C47.938 11.396 37.218.68 23.996.68 10.77.675.055 11.391.055 24.617l.001.001Z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
\ No newline at end of file
User-agent: *
Disallow: /graphs/
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="31" fill="none"><g opacity=".9"><path fill="url(#a)" d="M13 .4v29.3H7V6.3h-.2L0 10.5V5L7.2.4H13Z"/><path fill="url(#b)" d="M28.8 30.1c-2.2 0-4-.3-5.7-1-1.7-.8-3-1.8-4-3.1a7.7 7.7 0 0 1-1.4-4.6h6.2c0 .8.3 1.4.7 2 .4.5 1 .9 1.7 1.2.7.3 1.6.4 2.5.4 1 0 1.7-.2 2.5-.5.7-.3 1.3-.8 1.7-1.4.4-.6.6-1.2.6-2s-.2-1.5-.7-2.1c-.4-.6-1-1-1.8-1.4-.8-.4-1.8-.5-2.9-.5h-2.7v-4.6h2.7a6 6 0 0 0 2.5-.5 4 4 0 0 0 1.7-1.3c.4-.6.6-1.3.6-2a3.5 3.5 0 0 0-2-3.3 5.6 5.6 0 0 0-4.5 0 4 4 0 0 0-1.7 1.2c-.4.6-.6 1.2-.6 2h-6c0-1.7.6-3.2 1.5-4.5 1-1.3 2.2-2.3 3.8-3C25 .4 26.8 0 28.8 0s3.8.4 5.3 1.1c1.5.7 2.7 1.7 3.6 3a7.2 7.2 0 0 1 1.2 4.2c0 1.6-.5 3-1.5 4a7 7 0 0 1-4 2.2v.2c2.2.3 3.8 1 5 2.2a6.4 6.4 0 0 1 1.6 4.6c0 1.7-.5 3.1-1.4 4.4a9.7 9.7 0 0 1-4 3.1c-1.7.8-3.7 1.1-5.8 1.1Z"/></g><defs><linearGradient id="a" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient><linearGradient id="b" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient></defs></svg>
\ No newline at end of file
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>
\ No newline at end of file
@tailwind base;
@tailwind components;
@tailwind utilities;
svg {
display: inline-block
}
.app-text {
@apply bg-gradient-to-br from-orange-500 bg-clip-text text-transparent;
background-color: var(--pc);
text-shadow: 2px 2px 0px rgba(var(--primary-6),0.2);
}
body {
--primary-1: 214, 235, 233;
--primary-2: 182, 219, 216;
--primary-3: 150, 202, 199;
--primary-4: 118, 185, 182;
--primary-5: 76, 158, 165;
--primary-6: 94, 171, 165;
--primary-7: 76, 139, 148;
--primary-8: 58, 107, 132;
--primary-9: 40, 74, 117;
--primary-10: 22, 42, 102;
--white-bg: rgb(247, 247, 247);
--pc: rgb(var(--primary-6));
}
body:not([arco-theme=dark]) {
.graphs-list {
.arco-list-header {
background: var(--pc);
color: #fcf9f9;
}
}
}
body[arco-theme=dark] {
--primary-1: 35, 81, 78;
--primary-2: 43, 89, 86;
--primary-3: 51, 97, 94;
--primary-4: 59, 105, 102;
--primary-5: 67, 113, 110;
--primary-6: 75, 121, 118;
--primary-7: 83, 129, 126;
--primary-8: 91, 137, 134;
--primary-9: 99, 145, 142;
--primary-10: 107, 153, 150;
--color-text-2: rgba(230, 230, 230, 0.9);
--color-text-3: rgba(204, 204, 204, 0.9);
--color-text-4: rgba(179, 179, 179, 0.9);
--color-text-5: rgba(153, 153, 153, 0.9);
--color-text-6: rgba(128, 128, 128, 0.9);
--color-text-7: rgba(102, 102, 102, 0.9);
--color-text-8: rgba(77, 77, 77, 0.9);
--color-text-9: rgba(51, 51, 51, 0.9);
--color-text-10: rgba(26, 26, 26, 0.9);
--white-bg: rgb(217, 217, 217);
--pc: rgb(var(--primary-6));
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(200 200 200 / 0.5), 0 1px 2px -1px rgb(200 200 200 / 0.1);
}
.arco-input {
color: rgb(var(--primary-6));
}
.graphs-list {
.arco-list-header {
background: #3f3e3e;
color: #fcf9f9;
}
}
.app-text {
text-shadow: 2px 2px 2px rgb(255 255 255/0.2);
}
}
.monaco-editor.monaco-editor {
--vscode-editorLineNumber-activeForeground: var(--pc);
.line-numbers {
font-weight: bold;
}
.view-lines,
.decorationsOverviewRuler,
.invisible {
background: rgb(233, 242, 241);
}
.slider.slider {
border-radius: 5px;
background: var(rgb(--primary-6));
&:hover {
background: var(rgb(--primary-3));
}
}
}
.minimap {
background-color: rgb(94 171 165 / 0.1);
}
.monaco-editor .monaco-scrollable-element .current-line.current-line {
background-color: rgb(94 171 165 / 0.1);
border: none;
border-top: 2px solid rgb(238, 238, 238,0.5);
border-bottom: 2px solid rgb(238, 238, 238,0.5);
}
.table {
display: block;
}
.minimap-decorations-layer {
background: rgba(0, 128, 128, 0.1);
}
.margin-view-overlays {
background: rgb(0 128 128 / 14%);
}
.arco-tabs-content {
padding: 0;
}
.form-item label {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
display: block;
}
.gradient-text {
background: linear-gradient(to right, rgba(255, 165, 0, 0.8), rgba(0, 255, 64, 0.8));
animation: change-shadow 1s linear infinite alternate-reverse;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.gradient-text-animation {
background: linear-gradient(to right, rgba(255, 165, 0, 0.8), rgba(0, 255, 64, 0.8));
animation: change-shadow1 3s linear infinite alternate-reverse;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
@keyframes change-shadow1 {
0% {
background: linear-gradient(to right, rgba(255, 165, 0, 0.8), rgba(0, 255, 64, 0.8));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 4px 4px rgba(0, 255, 0, 0.2);
}
20% {
background: linear-gradient(to right, rgba(255, 68, 0, 0.8), rgba(183, 0, 255, 0.8));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 4px 4px rgba(255, 64, 0, 0.2);
}
40% {
background: linear-gradient(to right, rgba(0, 26, 255, 0.8), rgba(255, 81, 0, 0.8));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 4px 4px rgba(0, 26, 255, 0.2);
}
60% {
background: linear-gradient(to right, rgba(255, 0, 166, 0.8), rgba(0, 149, 255, 0.8));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 4px 4px rgba(251, 0, 255, 0.2);
}
80% {
background: linear-gradient(to right, rgba(221, 255, 0, 0.8), rgba(255, 0, 238, 0.8));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 4px 4px rgba(0, 255, 157, 0.2);
}
100% {
background: linear-gradient(to right, rgba(0, 85, 255, 0.8), rgba(255, 0, 0, 0.8));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 4px 4px rgba(255, 166, 0, 0.4);
}
}
.simple-mode {
border-radius: 10px;
overflow: hidden;
.arco-input-group {
.arco-input-inner-wrapper {
background: #fff;
border-radius: 10px;
}
}
}
.simple-mode-border {
border-radius: 5px;
overflow: hidden;
.arco-input-group {
.arco-input-inner-wrapper {
background: #fff;
border-radius: 5px;
@apply border border-[var(--pc)] transition-all;
&.arco-input-inner-wrapper-focus {
@apply rounded-[10px] border-2;
}
}
}
}
.bg-main {
background: rgb(var(--primary-6));
}
.arco-input-disabled:disabled {
color: rgb(var(--primary-3));
-webkit-text-fill-color: rgb(var(--primary-3));
}
.auto-h {
.arco-form-item-control {
min-height: 0;
}
>.arco-col {
flex: 1;
}
}
.sessions—box {
--color-neutral-3: rgba(0, 128, 128, 0.3);
}
.shadow-t {
box-shadow: 0 -5px 10px 0px rgba(130, 130, 130, 0.1);
}
\ No newline at end of file
body
overflow: hidden
background-color: var(--color-bg-2)
a
cursor: pointer
.tc
width: 480px
margin: 0 auto
text-align: center
.nav
display: flex
justify-content: space-between
align-items: center
position: absolute
top: 0
left: 0
width: 100vw
height: 60px
z-index: 2
box-sizing: border-box
padding: 10px
background-color: var(--color-bg-1)
color: var(--color-text-1)
box-shadow: 0 0 8px rgba(0, 0, 0, 0.08)
a
color: var(--color-text-1)
text-decoration: none
.main
width: 100vw
height: 100vh
position: absolute
left: 0
top: 0
background-color: var(--color-bg-2)
&:active
cursor: grab
.aside
position: absolute
height: 100vh
right: 0
top: 0
z-index: 3
background: #fff
box-sizing: border-box
padding: 15px
width: 400px
overflow: auto
label
display: block
.graph
background-image: radial-gradient(var(--color-fill-3) 1px, rgba(0, 0, 0, 0) 1px)
background-size: 12px 12px
width: 100vw
height: 100vh
.table
background: var(--color-bg-3)
color: var(--color-text-2)
border: 1px solid var(--color-border-2)
margin: 4px
border-radius: var(--border-radius-medium)
overflow: hidden
user-select: none
box-shadow: 0 0 6px rgba(0, 0, 0, 0.08)
&:hover, &-selected
border: 1px solid rgb(var(--primary-6))
box-shadow: 0 0 6px rgba(0, 0, 0, 0.12)
.table-settings
opacity: 1
&:active,
&-title
cursor: default
&.editable
&:active,
.table-title
cursor: move
&-title
display: flex
justify-content: space-between
align-items: center
padding: 0 8px
color: var(--color-text-1)
box-sizing: border-box
background: var(--color-fill-2)
font-weight: 600
&-settings
opacity: 0
&-name
max-width: 50%
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
&-comment
padding: 0 8px
font-size: 12px
color: var(--color-text-3)
background: var(--color-neutral-3)
&.no-comment
color: var(--color-text-4)
font-style: italic
.row
position: relative
padding: 0 8px 0 20px
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
border-top: 1px solid var(--color-border-2)
box-sizing: border-box
&:hover
opacity: .8
&.editable:hover
.field-type
display: none
.grip-setting
display: block
.grip-setting-btn
margin-left: 4px
.field-content
display: flex
justify-content: space-between
.field-type
font-size: 12px
color: var(--color-text-3)
text-transform: lowercase
.grip
width: 8px
height: 8px
border-radius: 50%
background: rgb(var(--primary-3))
position: absolute
right: 5px
top: 50%
transform: translateY(-50%)
cursor: pointer
.start-grip
right: auto
left: 5px
.grip-setting
position: absolute
right: 5px
top: -1.5px
display: none
.path-label
border: 1px solid var(--color-border-3)
background: var(--color-bg-3)
color: var(--color-text-1)
text-align: center
position: relative
z-index: 3
box-sizing: border-box
vertical-align: center
border-radius: 50%
padding: 0
width: 20px
height: 20px
line-height: 20px
font-size: 12px
font-family: Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto
.path-line
stroke: var(--color-border-3)
.table-tips
width: 240px
margin: -8px
.head
padding-bottom: 6px
margin-bottom: 6px
border-bottom: 1px solid var(--color-border-3)
.field-name
color: var(--color-text-1)
font-weight: 500
.field-type
margin-left: 8px
text-transform: lowercase
color: rgb(var(--orange-4))
.content
font-size: 12px
.field-item
display: flex
span
margin-right: 8px
color: var(--color-text-3)
.empty-value
color: var(--color-text-4)
font-style: italic
.drawer-btns
position: fixed
bottom: 10px
left: 0
right: 0
display: flex
justify-content: space-around
button
width: 40%
label
user-select: none
.left-table-nav
position: absolute
z-index: 101
width: 196px
padding: 8px 0 4px
left: 24px
top: 72px
background-color: var(--color-bg-5)
border-radius: 4px
box-shadow: 0 8px 24px 2px rgba(29, 41, 57, .14)
transition: all .2s
.table-nav-title
display: flex
justify-content: space-between
align-items: center
padding: 2px 8px 8px 16px
color: var(--color-text-2)
font-weight: bold
.table-nav-content
border-top: 1px solid var(--color-neutral-3)
.table-nav-menu
max-height: calc(100vh - 160px)
overflow-y: auto
.table-nav-search
padding: 8px 8px 0
.table-nav-count
margin-left: 4px
font-style: italic
.table-filter
background-color: var(--color-primary-light-1) !important
.arco-menu-light,
.arco-menu-light .arco-menu-item
background-color: var(--color-bg-5)
.arco-menu-light .arco-menu-item:hover,
.arco-menu-light .arco-menu-item.arco-menu-selected
background-color: var(--color-fill-2)
.arco-btn-secondary:not(.arco-btn-disabled)
background-color: transparent
.dragging
opacity: .3
.dropping:after
content: ' '
display: block
width: 100%
height: 10px
margin-top: 10px
background: var(--color-primary-light-1)
.flex
display: flex
align-items: center
justify-content: space-between
.graph-container
padding-top: 100px
max-height: calc(100vh - 100px)
overflow-y: auto
background-color: var(--color-bg-2)
.arco-list-wrapper
padding-bottom: 80px
.graph-list
max-width: min(1400px,90%)
margin: 0 auto
.arco-list-header
background: var(--color-fill-2)
.table-form
margin-bottom: 8px
.arco-form-item
margin-bottom: 0
.table-form-item
width: 100%
justify-content: space-between
.arco-space-item
width: 50%
.table-form-label
width: 55px
display: inline-block
.custom-radio-card
padding: 10px 16px
border: 1px solid var(--color-border-2)
border-radius: 4px
width: 100%
box-sizing: border-box
cursor: pointer
margin-top: 8px
margin-bottom: 8px
position: relative
.custom-radio-card-dot
height: 8px
width: 8px
display: inline-flex
align-items: center
justify-content: center
border-radius: 100%
border: 1px solid var(--color-border-2)
box-sizing: border-box
.custom-radio-card-title
color: var(--color-text-1)
font-size: 14px
font-weight: bold
margin-bottom: 4px
.custom-radio-card-secondary
color: var(--color-text-3)
.custom-radio-card:hover,
.custom-radio-card-checked,
.custom-radio-card:hover .custom-radio-card-dot,
.custom-radio-card-checked .custom-radio-card-dot
border-color: rgb(var(--primary-6))
.custom-radio-card-checked
background-color: var(--color-primary-light-1)
.custom-radio-card:hover .custom-radio-card-title,
.custom-radio-card-checked .custom-radio-card-title,
.custom-radio-card:hover .custom-radio-card-secondary,
.custom-radio-card-checked .custom-radio-card-secondary
color: rgb(var(--primary-6))
.custom-radio-card-checked .custom-radio-card-dot
background-color: rgb(var(--primary-6))
.delete-btn
position: absolute
right: 2px
top: 0
padding: 8px
color: var(--color-text-3)
font-size: 12px
&:hover
color: rgb(var(--primary-6))
.context-menu
width: 200px
.context-menu-item
display: flex
justify-content: space-between
align-items: center
.context-menu-line
margin: 4px 0
body[arco-theme=dark] .arco-home-key
background: #202022
border: 1px solid hsla(0, 0%, 100%, .12)
box-shadow: 0 1px 0 hsl(0deg 0% 100% / 12%)
color: hsla(0, 0%, 100%, .4)
.arco-home-key
display: flex
flex-direction: row
justify-content: center
align-items: center
padding: 6px
width: 17.21px
height: 20px
left: 117px
top: 6px
background: #fff
border: 1px solid #c9cdd4
box-sizing: border-box
box-shadow: 0 1px 0 #c9cdd4
border-radius: 3px
font-size: 12px
color: var(--color-text-3)
.index-bg
padding: 8rem 0 12rem 0
background-color: var(--color-primary-light-1)
background-image: url('/images/bg.svg')
background-size: cover
background-position: center
display: flex
flex-direction: column
align-items: center
justify-content: center
.mark
background-color: var(--color-primary-light-1)
width: auto
.index-video-container
position: relative
overflow: hidden
video
height: 700px
width: 100%
top: 0
left: 0
filter: blur(2px) brightness(.5)
position: relative
z-index: 1
object-fit: cover
.faq
max-width: 1200px
width: 100%
left: 50%
transform: translate(-50%, -50%)
margin: 0 auto
position: absolute
top: 50%
z-index: 2
padding: 20px
border-radius: 4px
color: #fff
h2
font-size: 2rem
font-weight: bolder
margin-bottom: 1rem
dt
font-size: 1.5rem
font-weight: bolder
margin-bottom: 1rem
dd
font-size: 1.5rem
margin: 0 0 2rem 0
.index-steps
margin: -10px 0 0 0
overflow: hidden
background-image: radial-gradient(rgba(0, 0, 0, .1) 1px, rgba(0, 0, 0, 0) 1px)
background-size: 12px 12px
.index-footer
display: flex
align-items: center
justify-content: space-around
flex-direction: column
padding: 150px
margin: -10px 0 0 0
overflow: hidden
background: rgba(0, 0, 0, 0.1)
color: #666
&>div
color: #000
font-size: 1.5rem
a span
color: #666
button span
color: #666
button svg
color: #666
.home {
color: #fff;
font-family: "Roboto Condensed", sans-serif;
background-image: url(https://images.unsplash.com/photo-1508389377389-b8221c0bcc9e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80);
background-size: 110%;
background-position: 0% 0%;
margin: 0;
animation: change-background 15s linear infinite alternate-reverse;
width: 100%;
height: 100vh;
@keyframes change-background {
0% {
background-size: 100%;
}
50% {
background-size: 120%;
// background-position: 0% 5%;
}
0% {
background-size: 100%;
}
}
@import url("https://fonts.googleapis.com/css?family=Roboto+Condensed&display=swap");
.main {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center;
background: rgb(49, 177, 134);
background: linear-gradient(0deg,
rgba(49, 177, 109, 0.763) 0%,
rgba(49, 173, 177, 0.698) 50%,
rgba(49, 177, 173, 0.6) 100%);
font-family: "Mona Sans", sans-serif;
.container {
width: 100%;
margin: 15% auto;
}
.col-4 {
width: 25%;
float: left;
}
h1 {
font-size: 38px;
text-transform: uppercase;
line-height: 1;
animation: change-shadow 1s linear infinite alternate-reverse;
}
@keyframes change-shadow {
0% {
text-shadow: 4px 4px rgba(0, 255, 0, 0.2);
}
100% {
text-shadow: 4px 4px rgba(255, 166, 0, 0.4);
}
}
.box {
width: 150px;
height: 50px;
}
.box .label {
font-size: 30px;
}
}
.text-stroke {
font-size: 35px;
letter-spacing: 4px;
fill: none;
stroke: url(#linear-gradient3);
stroke-width: 1;
stroke-dasharray: 100%;
stroke-dashoffset: 100%;
animation: textStrokeAnim 8000ms linear both alternate-reverse;
animation-iteration-count: infinite;
}
@keyframes textStrokeAnim {
0% {
stroke-dasharray: 100%;
stroke-dashoffset: 100%;
fill: url(#linear-gradient1);
}
85% {
stroke-dasharray: 0%;
stroke-dashoffset: 90%;
stroke: url(#linear-gradient1);
fill:transparent;
}
100% {
stroke-dasharray: 0%;
stroke-dashoffset: 0%;
fill: url(#linear-gradient1);
filter: drop-shadow(2px 2px 10px rgba(0, 255, 0, 0.2));
}
}
}
\ No newline at end of file
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #abb2bf;
background: #282c34;
}
.hljs-comment,
.hljs-quote {
color: #5c6370;
font-style: italic;
}
.hljs-doctag,
.hljs-keyword,
.hljs-formula {
color: #c678dd;
}
.hljs-section,
.hljs-name,
.hljs-selector-tag,
.hljs-deletion,
.hljs-subst {
color: #e06c75;
}
.hljs-literal {
color: #56b6c2;
}
.hljs-string,
.hljs-regexp,
.hljs-addition,
.hljs-attribute,
.hljs-meta-string {
color: #98c379;
}
.hljs-built_in,
.hljs-class .hljs-title {
color: #e6c07b;
}
.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-type,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-number {
color: #d19a66;
}
.hljs-symbol,
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-title {
color: #61aeee;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-link {
text-decoration: underline;
}
.message pre {
background-color: #272c34;
border-radius: 8px;
font-size: 0.8rem;
padding: 0.4rem 1rem;
margin-bottom: 10px;
max-width: 100%;
overflow: auto;
}
.message .hljs {
/* background-color: transparent; */
}
.message table {
font-size: 0.8em;
}
.message table thead tr {
background-color: #64748b40;
text-align: left;
}
.message table th,
.message table td {
padding: 0.6rem 1rem;
}
.message table tbody tr:last-of-type {
border-bottom: 2px solid #64748b40;
}
.message>* {
max-width: 100%;
min-width: 100%;
}
.message{
width: 100%;
display: grid;
}
\ No newline at end of file
.message {
font-size: 16px;
position: relative;
z-index: 0
}
.message h1,
.message h2,
.message h3,
.message h4,
.message h5,
.message h6,
.message p {
margin: 1em 0
}
.message h1:first-child,
.message h2:first-child,
.message h3:first-child,
.message h4:first-child,
.message h5:first-child,
.message h6:first-child,
.message p:first-child {
margin-top: 0
}
.message h1:last-child,
.message h2:last-child,
.message h3:last-child,
.message h4:last-child,
.message h5:last-child,
.message h6:last-child,
.message p:last-child {
margin-bottom: 0
}
.message p {
line-height: 1.5
}
@media only screen and (max-width: 375px) {
.message p code {
word-break: break-all
}
}
.message ol,
.message ul {
list-style-type: disc;
margin-bottom: 1.7em;
padding-left: 2.5em
}
@media only screen and (max-width: 768px) {
.message ol,
.message ul {
list-style-position: inside;
padding-left: 0
}
}
.message ol {
list-style-type: decimal
}
.message a {
border-bottom: 1px solid #0069ff;
color: #0069ff;
text-decoration: none;
transition: color .25s, border-bottom-color .25s
}
.message a:focus,
.message a:hover {
border-bottom-color: #1253fa;
color: #1253fa
}
.message a code {
position: relative;
z-index: -1
}
.message blockquote {
border-left: 4px solid #d6dcea;
color: #8a96b5;
display: block;
font-size: 18px;
margin: 0 0 1.25em 1.25em;
padding: 0 0 0 1.5em
}
.message blockquote a {
border-bottom: 1px dotted #8a96b5;
color: #8a96b5
}
.message blockquote a:focus,
.message blockquote a:hover {
border-bottom-style: solid
}
.message figure,
.message img {
border: 2px solid #e3e8f4;
border-radius: 16px;
display: block;
margin: 1rem auto;
max-width: 100%
}
.message img[align=left] {
float: unset;
margin-left: 0
}
.message img[align=right] {
float: unset;
margin-right: 0
}
.message figure {
overflow: hidden;
padding: 1rem
}
.message figure:has(img[align=left]) {
margin-left: 0;
width: -moz-fit-content;
width: fit-content
}
.message figure:has(img[align=right]) {
margin-right: 0;
width: -moz-fit-content;
width: fit-content
}
.message figure img {
border: none;
border-radius: 0;
margin: 0 auto
}
.message figure figcaption {
border-top: 1px solid #e3e8f4;
background: #eff2fb;
font-size: .9em;
text-align: center;
padding: 1rem;
margin: 1rem -1rem -1rem
}
.message code,
.message pre {
background-color: #e3e8f4;
border-radius: 8px;
color: #24335a;
font-size: 14px;
line-height: 1.4em;
padding: 3px
}
.message pre {
background: #081b4b;
color: #fff;
border-radius: 16px;
display: block;
margin: 1em 0;
overflow: auto;
overflow-wrap: normal;
padding: 1em;
white-space: normal;
word-wrap: normal
}
.message pre code,
.message pre.prefixed code ol li:before {
color: #fff
}
.message pre.prefixed.line_numbers code ol li:before {
border-right-color: hsla(0, 0%, 100%, .5)
}
.message pre code {
background: 0;
border-radius: 0;
color: inherit;
padding: 0;
white-space: pre
}
.message table {
border-collapse: collapse;
width: 100%
}
.message table thead tr {
border-bottom: 1px dashed #d6dcea
}
.message table thead tr th {
color: #081b4b;
font-weight: 600;
padding: 16px 8px
}
.message table tbody tr {
border-bottom: 1px solid #d6dcea
}
.message table tbody tr td {
color: #24335a;
padding: 24px 8px
}
.message table td:first-child,
.message table th:first-child {
padding-left: 0
}
.message table td:last-child,
.message table th:last-child {
padding-right: 0
}
.message .vimeo,
.message .wistia,
.message .youtube {
display: block;
height: auto;
margin: 1em auto;
max-width: 100%
}
.message .rsvp {
background: transparent;
border: 1px solid #0069ff;
border-radius: 3px;
box-sizing: border-box;
color: #0069ff;
cursor: pointer;
font-size: 16px;
font-weight: 700;
line-height: 1;
padding: 8px 24px;
transition: background .25s ease
}
.message .rsvp:disabled {
border-color: #aab3ca;
color: #aab3ca;
cursor: not-allowed
}
.message .rsvp:not(:disabled):focus,
.message .rsvp:not(:disabled):hover {
background: #c8dfff
}
.message .terminal {
background: transparent;
border: 1px solid #0069ff;
border-radius: 3px;
box-sizing: border-box;
color: #0069ff;
cursor: pointer;
font-size: 16px;
font-weight: 700;
line-height: 1;
padding: 8px 24px;
transition: background .25s ease
}
.message .terminal:disabled {
border-color: #aab3ca;
color: #aab3ca;
cursor: not-allowed
}
.message .terminal:not(:disabled):focus,
.message .terminal:not(:disabled):hover {
background: #c8dfff
}
.message .columns {
display: flex;
flex-flow: row nowrap;
margin: -1em
}
@media only screen and (max-width: 768px) {
.message .columns {
flex-flow: row wrap
}
}
@media only screen and (max-width: 375px) {
.message .columns {
flex-flow: column
}
}
.message .columns .column {
flex-basis: 100%;
flex-grow: 1;
padding: 1em
}
@media only screen and (max-width: 768px) {
.message .columns .column {
flex-basis: auto
}
}
.message details {
background: #f9fafe;
border-radius: 16px;
padding: 1em;
margin: 1em 0
}
.message details[open] summary {
border-bottom: 1px solid #e3e8f4;
padding: 0 1em 1em 0;
margin: 0 0 1em
}
.message details[open] summary:after {
top: calc(50% - 3px);
transform: translateY(-50%) rotate(225deg)
}
.message details summary {
cursor: pointer;
list-style: none;
padding: 0 1em 0 0;
position: relative
}
.message details summary::-webkit-details-marker,
.message details summary::marker {
display: none
}
.message details summary:after {
content: "";
display: block;
position: absolute;
top: 50%;
right: 4px;
width: 7px;
height: 7px;
border: solid #8a96b5;
border-width: 0 2px 2px 0;
transform: translateY(-50%) rotate(45deg)
}
.message .twitter .twitter-tweet-rendered {
margin: 1rem auto
}
.message .twitter[align=left] {
float: unset
}
.message .twitter[align=left] .twitter-tweet-rendered {
margin-left: 0
}
.message .twitter[align=right] {
float: unset
}
.message .twitter[align=right] .twitter-tweet-rendered {
margin-right: 0
}
.message .instagram .instagram-media {
margin: 1rem auto !important
}
.message .instagram[align=left] .instagram-media {
margin-left: 0 !important
}
.message .instagram[align=right] .instagram-media {
margin-right: 0 !important
}
.message .slideshow {
margin: 0 auto;
position: relative;
overflow: hidden
}
.message .slideshow .action {
background-color: hsla(0, 10%, 70%, .2);
border: 1px solid #666;
border-radius: 50%;
color: #666;
cursor: pointer;
font-size: 32px;
height: 30px;
line-height: 22px;
position: absolute;
text-align: center;
top: 50%;
width: 30px;
z-index: 10
}
.message .slideshow .action.left {
left: 10px
}
.message .slideshow .action.right {
right: 10px
}
.message .slideshow .action:hover {
background-color: hsla(0, 10%, 70%, .1);
border: 1px solid #ccc;
color: #ccc
}
.message .slideshow .slides {
display: flex;
overflow-x: auto;
-ms-scroll-snap-type: x mandatory;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch
}
.message .slideshow .slides>img {
align-items: center;
display: flex;
flex-shrink: 0;
font-size: 100px;
height: 100%;
justify-content: center;
margin-right: 50px;
scroll-snap-align: start;
position: relative;
transform-origin: center center;
transform: scale(1);
transition: transform .5s;
width: 100%
}
.message .hash-anchor {
border-bottom: none;
color: #8a96b5;
margin-right: .5em;
text-decoration: none
}
.message .hash-anchor:focus,
.message .hash-anchor:hover {
color: #24335a
}
.message .hash-anchor:before {
content: "#"
}
.message mark {
background: rgba(242, 201, 76, .35);
border-radius: 8px;
color: inherit;
display: inline;
line-height: calc(1.4em + 1px);
padding: 0 2px
}
.message mark mark {
background: none;
border-radius: 0;
border-radius: initial;
line-height: 1.4em;
padding: initial
}
.message pre code mark {
background: rgba(65, 255, 244, .35)
}
.message pre code mark mark {
background: none
}
.message .callout {
background-color: #c8dfff;
border-radius: 16px;
color: #002c9b;
display: block;
font-size: 15px;
line-height: 1.5em;
margin: 0 0 1.5em;
padding: 1em 1.25em;
position: relative;
z-index: 0
}
.message .callout>:first-child {
margin-top: 0
}
.message .callout>:last-child {
margin-bottom: 0
}
.message .callout .callout-label {
background: rgba(0, 0, 0, .1);
border-radius: 16px 16px 0 0;
font-size: 15px;
margin: -1em -1.25em 0;
padding: .75em 1.25em;
text-align: center
}
.message .code-label {
background-color: #d6dcea;
border-radius: 16px 16px 0 0;
color: #24335a;
display: block;
font-size: 14px;
margin: 1em 0 0;
padding: .5em 1em;
position: relative;
text-align: center;
z-index: 2
}
.message .code-label+pre {
border-radius: 0 0 16px 16px;
margin: 0 0 1em
}
.message .code-label+.code-toolbar {
margin: 0 0 1em
}
.message .code-label+.code-toolbar pre {
border-radius: 0 0 16px 16px
}
.message pre.prefixed code {
white-space: normal
}
.message pre.prefixed code ol {
list-style: none;
margin: 0;
padding: 0
}
.message pre.prefixed code ol li {
margin: 0;
padding: 0;
white-space: pre
}
.message pre.prefixed code ol li:before {
content: attr(data-prefix);
display: inline-block;
margin: 0 10px 0 5px;
text-align: right;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
.message pre.prefixed.line_numbers code ol li {
margin: 0 0 0 -5px
}
.message pre.prefixed.line_numbers code ol li:before {
border-right: 1px solid hsla(0, 0%, 100%, .5);
padding-right: 5px
}
.message pre[class*=language-]>code {
background: transparent
}
.message pre[class*=language-]>code .token.entity,
.message pre[class*=language-]>code .token.operator,
.message pre[class*=language-]>code .token.punctuation,
.message pre[class*=language-]>code .token.url,
.message pre[class*=language-]>code.language-css .token.string,
.message pre[class*=language-]>code.style .token.string {
color: #eff2fb
}
.message pre[class*=language-]>code .token.cdata,
.message pre[class*=language-]>code .token.comment,
.message pre[class*=language-]>code .token.doctype,
.message pre[class*=language-]>code .token.prolog {
color: #e3e8f4
}
.message pre[class*=language-]>code .token.atrule,
.message pre[class*=language-]>code .token.builtin,
.message pre[class*=language-]>code .token.char,
.message pre[class*=language-]>code .token.constant,
.message pre[class*=language-]>code .token.deleted,
.message pre[class*=language-]>code .token.delimiter,
.message pre[class*=language-]>code .token.inserted,
.message pre[class*=language-]>code .token.keyword,
.message pre[class*=language-]>code .token.property,
.message pre[class*=language-]>code .token.selector,
.message pre[class*=language-]>code .token.symbol,
.message pre[class*=language-]>code .token.tag {
color: #4994ff
}
.message pre[class*=language-]>code .token.attr-name,
.message pre[class*=language-]>code .token.boolean,
.message pre[class*=language-]>code .token.color,
.message pre[class*=language-]>code .token.string,
.message pre[class*=language-]>code .token.variable {
color: #29e3ab
}
.message pre[class*=language-]>code .token.attr-value,
.message pre[class*=language-]>code .token.class-name,
.message pre[class*=language-]>code .token.function {
color: #ff4084
}
.message pre[class*=language-]>code .token.important,
.message pre[class*=language-]>code .token.number,
.message pre[class*=language-]>code .token.regex {
color: #00c6ff
}
.message pre[class*=language-]>code,
.message pre[class*=language-]>code .token {
text-shadow: none
}
.message pre[class*=language-]>code.language-css .token.string,
.message pre[class*=language-]>code.style .token.string {
background: none
}
.message pre[class*=language-]>code .token {
background: none;
border-radius: 0;
display: inline;
font-weight: 400;
margin: 0;
padding: 0
}
.message pre[class*=language-]>code .token.namespace {
opacity: .7
}
.message pre[class*=language-]>code .token.entity,
.message pre[class*=language-]>code .token.operator,
.message pre[class*=language-]>code .token.url {
background: none
}
.message pre[class*=language-]>code .token.bold,
.message pre[class*=language-]>code .token.important {
font-weight: 700
}
.message pre[class*=language-]>code .token.italic {
font-style: italic
}
.message .code-toolbar {
margin: 1em 0;
position: relative
}
.message .code-toolbar>pre {
margin: 0
}
.message .code-toolbar>.toolbar {
position: absolute;
right: .2em;
top: .3em
}
.message .code-toolbar>.toolbar>.toolbar-item {
display: inline-block
}
.message .code-toolbar>.toolbar>.toolbar-item>a {
text-decoration: none
}
.message .code-toolbar>.toolbar>.toolbar-item>button {
background: none;
border: 0;
color: inherit;
font: inherit;
line-height: normal;
overflow: visible;
padding: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
.message .code-toolbar>.toolbar>.toolbar-item>a,
.message .code-toolbar>.toolbar>.toolbar-item>button {
background: #fff;
border-radius: 8px;
cursor: pointer;
height: 24px;
padding: 0 8px;
transition: color .25s, background .25s;
width: auto
}
.message .code-toolbar>.toolbar>.toolbar-item>a:focus,
.message .code-toolbar>.toolbar>.toolbar-item>a:hover,
.message .code-toolbar>.toolbar>.toolbar-item>button:focus,
.message .code-toolbar>.toolbar>.toolbar-item>button:hover {
background: #4994ff;
color: #000c2b
}
.message .code-toolbar>.toolbar>.toolbar-item>a:focus span,
.message .code-toolbar>.toolbar>.toolbar-item>a:hover span,
.message .code-toolbar>.toolbar>.toolbar-item>button:focus span,
.message .code-toolbar>.toolbar>.toolbar-item>button:hover span {
color: #fff
}
.message .code-toolbar>.toolbar>.toolbar-item>a,
.message .code-toolbar>.toolbar>.toolbar-item>button,
.message .code-toolbar>.toolbar>.toolbar-item>span {
color: #24335a;
font-size: .9em;
margin: .5em;
padding: .1em 8px
}
.message pre code .secondary-code-label {
color: #00c6ff;
margin: 0 0 4px
}
.message .table-wrapper {
overflow-x: auto;
width: 100%
}
.message .image-compare {
position: relative
}
.message .image-compare .image-left,
.message .image-compare .image-right {
height: 100%;
-o-object-fit: contain;
object-fit: contain;
position: absolute;
width: 100%
}
.message .image-compare .image-left {
-webkit-clip-path: polygon(0 0, var(--value) 0, var(--value) 100%, 0 100%);
clip-path: polygon(0 0, var(--value) 0, var(--value) 100%, 0 100%);
margin: 0
}
.message .image-compare .image-right {
-webkit-clip-path: polygon(100% 0, var(--value) 0, var(--value) 100%, 100% 100%);
clip-path: polygon(100% 0, var(--value) 0, var(--value) 100%, 100% 100%);
margin: 0
}
.message .image-compare .control-arrow {
width: 30px;
height: 30px;
position: absolute;
top: 50%;
left: calc(var(--value) - 13px);
color: #fff;
z-index: 90
}
.message .image-compare .control {
background-color: transparent;
box-sizing: border-box;
font-family: inherit;
height: 100%;
outline: none;
position: absolute;
top: 0;
width: 100%;
z-index: 100
}
.message .image-compare .control::-moz-range-thumb {
background-color: #fff;
border: none;
cursor: ew-resize;
height: 100%;
width: 2px
}
.message .image-compare .control::-webkit-slider-thumb {
background-color: #fff;
border: none;
cursor: ew-resize;
height: 100%;
width: 2px
}
.message .image-compare .control::-moz-range-track {
background: transparent;
background-size: 100%;
box-sizing: border-box
}
.message .image-compare .control::-webkit-slider-runnable-track {
background: transparent;
background-size: 100%;
box-sizing: border-box;
height: 100%
}
.message .image-compare .control,
.message .image-compare .control::-webkit-slider-runnable-track,
.message .image-compare .control::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none
}
.message .callout.note {
background-color: #d4efee;
color: #005955
}
.message .callout.warning {
background-color: #ffe2e0;
color: #820800
}
.message .callout.info {
background-color: #c8dfff;
color: #002c9b
}
.message .callout.draft {
background-color: #ffc0ff;
color: #606
}
.message pre[class*=environment-]>code .token.entity,
.message pre[class*=environment-]>code .token.operator,
.message pre[class*=environment-]>code .token.punctuation,
.message pre[class*=environment-]>code .token.url,
.message pre[class*=environment-]>code.language-css .token.string,
.message pre[class*=environment-]>code.style .token.string {
color: #aab3ca
}
.message pre[class*=environment-]>code .token.cdata,
.message pre[class*=environment-]>code .token.comment,
.message pre[class*=environment-]>code .token.doctype,
.message pre[class*=environment-]>code .token.prolog {
color: #8a96b5
}
.message pre[class*=environment-]>code .token.atrule,
.message pre[class*=environment-]>code .token.builtin,
.message pre[class*=environment-]>code .token.char,
.message pre[class*=environment-]>code .token.constant,
.message pre[class*=environment-]>code .token.deleted,
.message pre[class*=environment-]>code .token.delimiter,
.message pre[class*=environment-]>code .token.inserted,
.message pre[class*=environment-]>code .token.keyword,
.message pre[class*=environment-]>code .token.property,
.message pre[class*=environment-]>code .token.selector,
.message pre[class*=environment-]>code .token.symbol,
.message pre[class*=environment-]>code .token.tag {
color: #0069ff
}
.message pre[class*=environment-]>code .token.attr-name,
.message pre[class*=environment-]>code .token.boolean,
.message pre[class*=environment-]>code .token.color,
.message pre[class*=environment-]>code .token.string,
.message pre[class*=environment-]>code .token.variable {
color: #08966b
}
.message pre[class*=environment-]>code .token.attr-value,
.message pre[class*=environment-]>code .token.class-name,
.message pre[class*=environment-]>code .token.function {
color: #e0276a
}
.message pre[class*=environment-]>code .token.important,
.message pre[class*=environment-]>code .token.number,
.message pre[class*=environment-]>code .token.regex {
color: #225196
}
.message pre[class*=environment-] .secondary-code-label {
color: #081b4b
}
.message pre[class*=environment-] mark {
background: rgba(242, 201, 76, .35)
}
.message pre[class*=environment-] mark mark {
background: none
}
.message pre.environment-local {
background: #e3e8f4;
color: #4d5b7c
}
.message pre.environment-local code,
.message pre.environment-local.prefixed code ol li:before {
color: #4d5b7c
}
.message pre.environment-local.prefixed.line_numbers code ol li:before {
border-right-color: rgba(77, 91, 124, .5)
}
.message pre.environment-second {
background: rgba(200, 223, 255, .5);
color: #002c9b
}
.message pre.environment-second code,
.message pre.environment-second.prefixed code ol li:before {
color: #002c9b
}
.message pre.environment-second.prefixed.line_numbers code ol li:before {
border-right-color: rgba(0, 44, 155, .5)
}
.message pre.environment-third {
background: hsla(4, 100%, 94%, .5);
color: #820800
}
.message pre.environment-third code,
.message pre.environment-third.prefixed code ol li:before {
color: #820800
}
.message pre.environment-third.prefixed.line_numbers code ol li:before {
border-right-color: rgba(130, 8, 0, .5)
}
.message pre.environment-fourth {
background: rgba(212, 239, 238, .5);
color: #005955
}
.message pre.environment-fourth code,
.message pre.environment-fourth.prefixed code ol li:before {
color: #005955
}
.message pre.environment-fourth.prefixed.line_numbers code ol li:before {
border-right-color: rgba(0, 89, 85, .5)
}
.message pre.environment-fifth {
background: rgba(255, 222, 255, .5);
color: #606
}
.message pre.environment-fifth code,
.message pre.environment-fifth.prefixed code ol li:before {
color: #606
}
.message pre.environment-fifth.prefixed.line_numbers code ol li:before {
border-right-color: rgba(102, 0, 102, .5)
}
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./app/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
}
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="-0.01051612850278616 0.0012276958441361785 50.34043502807617 50.378292083740234"><defs><linearGradient id="9dae7e44-bfbe-46a9-9f34-c205d01f0cfa" y1="18.98" x2="48.96" y2="18.98" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#45818E"></stop><stop offset=".47" stop-color="#93C47D"></stop><stop offset=".99" stop-color="#F6B26B"></stop></linearGradient><linearGradient id="37f83904-b3af-44cd-9dd3-e21b50a0287b" x1="1.42" y1="31.44" x2="50.36" y2="31.44" href="#9dae7e44-bfbe-46a9-9f34-c205d01f0cfa"></linearGradient></defs><path d="M7.59 7.21A24.9 24.9 0 0 1 26.67.05 25.28 25.28 0 0 1 49 16.79H30.24c0-2.83.05-5.67 0-8.5a1.65 1.65 0 0 0-2.43-1.39 14.2 14.2 0 0 0-1.76 1.62C20.91 13.7 15.7 18.83 10.58 24a1.64 1.64 0 0 0 1 2.81c7.66.07 15.33 0 23 0Q29 32.39 23.5 38c0-2.07.06-4.16 0-6.24a1.65 1.65 0 0 0-1.84-1.48H4.93A4.84 4.84 0 0 1 0 25.79 24.78 24.78 0 0 1 7.59 7.21z" fill="url(#9dae7e44-bfbe-46a9-9f34-c205d01f0cfa)"></path><path d="M15.81 23.5c3.65-3.69 7.3-7.38 11-11 .08 2 0 4.11.06 6.16a1.69 1.69 0 0 0 1.84 1.5h16.76a4.83 4.83 0 0 1 4.85 4.48 24.79 24.79 0 0 1-4.28 14.64c.81 3.07 1.67 6.12 2.45 9.2-3.12-.66-6.17-1.62-9.26-2.4A25.18 25.18 0 0 1 1.42 33.6h18.75c0 2.83-.05 5.66 0 8.49a1.64 1.64 0 0 0 2.23 1.46 9.55 9.55 0 0 0 1.67-1.45c5.2-5.27 10.5-10.45 15.68-15.73a1.63 1.63 0 0 0-1.06-2.82c-7.61-.06-15.25.04-22.88-.05z" fill="url(#37f83904-b3af-44cd-9dd3-e21b50a0287b)"></path></svg>
\ No newline at end of file
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules","backend/*"]
}
import { ModelExporter, Parser } from '@dbml/core';
/**
* It takes a table dictionary and a link dictionary and returns a SQL string
* @param tableDict - a dictionary of tables, where the key is the table ID and the value is the tableobject
* @param linkDict - a dictionary of links, where the key is the link id and the value is the linkobject
* @param [databaseType=postgres] - The type of database you want to export to.
* @returns SQL string.
*/
const exportSQL = (tableDict, linkDict, databaseType = 'postgres') => {
const combined = {
name: 'public',
note: '',
tables: Object.values(tableDict).map(table => {
return {
name: table.name,
note: table.note,
fields: table.fields.map(field => {
return {
...field,
type: {
// To lower case because of typing 'BIGINT' with upper case and increment get wrong pg sql type when export
type_name: field.type.toLowerCase(),
args: null,
},
};
}),
};
}),
enums: [],
tableGroups: [],
refs: Object.values(linkDict).map(ref => {
return {
...ref,
endpoints: ref.endpoints.map(endpoint => {
return {
...endpoint,
tableName: tableDict[endpoint.id].name,
fieldNames: [
tableDict[endpoint.id].fields.find(
field => field.id == endpoint.fieldId
).name,
],
};
}),
};
}),
};
const database = Parser.parse(combined, 'json');
const sql = ModelExporter.export(database, databaseType, false);
return sql;
};
export default exportSQL;
const gpt35 = {
name: 'gpt-3.5-turbo-16k-0613',
temperature: 0,
frequency_penalty: 0.0,
presence_penalty: 0.0,
};
const gpt4 = {
name: 'gpt-4-0613',
temperature: 0,
frequency_penalty: 0.0,
presence_penalty: 0.0,
};
export const models = [gpt35, gpt4];
export const getModel = (name: string) => {
for (const model of models) {
if (model.name === name) {
return model;
}
}
return gpt35;
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment