Commit 97f2750d authored by jaden's avatar jaden 🏄

Initial commit

parents
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
# next.js
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
{
"extends": "next/core-web-vitals",
"rules": {
"react-hooks/rules-of-hooks": "off", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "off",// Checks effect dependencies
}
}
name: Build and Push Docker Image
on:
push:
branches:
- fusionTeach
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: jadenxiong/chat-query:fusionTeach
build-args: |
NEXT_PUBLIC_BACKEND_URL=${{ secrets.NEXT_PUBLIC_BACKEND_URL }}
NEXT_PUBLIC_OPEN_AI_API_KEY=${{ secrets.NEXT_PUBLIC_OPEN_AI_API_KEY }}
OPENAI_PROXY_URL=${{ secrets.OPENAI_PROXY_URL }}
# 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
public/service-worker.js
xflow-docs
draw-app
packages
react-live-runner-swc
**/*.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 技术,通过自然语言实现数据查询。
## 演示
> [在线预览](https://query.fusiontech.cn/)
> [demo gif](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 调用。
- BI分析、Function 调用、react代码沙箱、支持导入npm包。
## 应用场景🎬
+ 从低代码到零代码开发。
+ 非业务人员快速进行数据分析。
+ 更多应用场景待探索...
## 部署
+ docker compose
```shell
NEXT_PUBLIC_OPEN_AI_API_KEY="sk-..." docker-compose up
```
+ chat-query和chat-query-backend都是支持docker构建和提供docker镜像.
## 开发环境设置
> 👏 欢迎参与 Chat-Query 的建设。
+ 后端:
```js
pnpm install
pnpm start:dev
```
- 在 .env 文件中添加
```shell
#openai
OPEN_AI_API_KEY='sk-...'
MODEL_NAME="gpt-3.5-turbo-16k-0613"
BASE_URL='https://open-ai-xyy.deno.dev/v1'
# database
DB_HOST="139.198.179.193"
DB_PORT=32094
```
+ 前端
```js
pnpm install
pnpm dev
```
- 在 .env 文件中添加
```shell
NEXT_PUBLIC_OPEN_AI_API_KEY='sk-...'
OPENAI_PROXY_URL="https://open-ai-xyy.deno.dev/"
NEXT_PUBLIC_BACKEND_URL="http://localhost:3001/"
```
## 系统架构
![架构](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)
# CHAT-QUERY
[zh](./README-zh.md)
> Chat-Query is a data query implementation based on metadata models and AI technology through natural language.
## Demo
> [Online Preview](https://chat-query.vercel.app/)
> [demo gif](https://cdn.glitch.me/fd139a45-4a65-41b6-9634-41617ab20cdc/%E6%BC%94%E7%A4%BA.gif?v=1686907695067)
+ **Features🐂:**
- Supports DDL, DBML and database reverse parsing import, AI automatically generates business models.
- Provides basic CRUD functions of business models, AI intelligent analysis, supports model export to DDL, DBML and synchronization with the database.
- Combines models and AI to implement natural language data queries, can be added to the query list and called via API.
- BI analysis, Function call, react code sandbox, supports importing npm packages.
## Application Scenarios🎬
+ From low-code to no-code development.
+ Non-business personnel can quickly perform data analysis.
+ More application scenarios to be explored...
## Deployment
+ docker compose
```shell
NEXT_PUBLIC_OPEN_AI_API_KEY="sk-..." docker-compose up
```
+ Both chat-query and chat-query-backend support docker build and provide docker images.
## Development Environment Setup
> 👏 Welcome to participate in the construction of Chat-Query.
+ Backend:
```js
pnpm install
pnpm start:dev
```
- Add in .env file
```shell
#openai
OPEN_AI_API_KEY='sk-...'
MODEL_NAME="gpt-3.5-turbo-16k-0613"
BASE_URL='https://open-ai-xyy.deno.dev/v1'
# database
DB_HOST="139.198.179.193"
DB_PORT=32094
```
+ Frontend
```js
pnpm install
pnpm dev
```
- Add in .env file
```shell
NEXT_PUBLIC_OPEN_AI_API_KEY='sk-...'
OPENAI_PROXY_URL="https://open-ai-xyy.deno.dev/"
NEXT_PUBLIC_BACKEND_URL="http://localhost:3001/"
```
## 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 '.';
export type DBInfo = {
host: string;
port: number;
user: string;
password: string;
database: string;
client: 'mysql2';
name?: string;
};
export interface Query {
schemaId: string;
DbID: string;
name: string;
content: {
executions: {
content: string;
type: string;
};
params: Record<string, any>;
info: { queryDescription: string; queryName: string };
functions?: string;
filed: 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: Query) {
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 });
}
static updateQuery(queryId: string, functions: string) {
return backendApi.put(`/query/${queryId}`, {
functions,
});
}
}
import getConfig from 'next/config';
import APi, { backendApi } from '.';
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 },
} = getConfig();
export interface schemaParams {
type: 'schema' | 'table';
name?: string;
}
export default class getSchema {
static getTableList(config: schemaParams) {
return APi.post(apiPath + 'getSchema', config);
}
}
import { ChatMessage } from './../../components/AITool/index';
import APi, { backendApi } from '.';
import { get, isArray, max, min, pick } from 'lodash';
import { Options } from 'redaxios';
import OpenAI from './openAI';
export interface View {
type: 'schema' | 'table';
name?: string;
}
const privateConfig: Options & {
timeout: number;
} = {
timeout: 1000 * 60,
};
function extractCodeBlocks(markdownText: string, lang = null) {
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
const codeBlocks = [];
let match;
while ((match = codeBlockRegex.exec(markdownText)) !== null) {
if (lang === null || match[1] === lang) {
codeBlocks.push(match[2]);
}
}
return codeBlocks;
}
export default class getView {
static getViewComponent(params: { props: Record<string, any>; need: string }) {
return new Promise((res, rej) => {
OpenAI.request(
[
{
role: 'user',
content: `我正在使用react-live作为一个实时编辑和编译React组件的工具。请根据我的需求,输出一段能在react-live运行的代码,只需要导出默认组件,不用挂载dom。可用的作用域如下:
{
data,
import: {
'styled-components':{
'描述':'A css in js Framework,Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress'
},
'echarts':{
'描述':'Apache ECharts is a powerful, interactive charting and data visualization library for browser.',
'用法': "import echarts from 'echarts';"
},
'echarts-for-react':{
'描述':'Apache ECharts is a powerful, interactive charting and data visualization library for browser.',
'用法': "import React from 'react';
import ReactECharts from 'echarts-for-react'; // or var ReactECharts = require('echarts-for-react');
<ReactECharts
option={this.getOption()}
notMerge={true}
lazyUpdate={true}
theme={"theme_name"}
onChartReady={this.onChartReadyCallback}
onEvents={EventsDict}
opts={}
/>"
},
},
}
其中import可供导入的库,除此之外不能使用其它第三方库。 key为包名,value 是包的描述。其它每个属性将作为单独全局变量,变量名为属性名。请确保默认导出的组件接受null作为参数,并将传入props取自模块全局作用域变量data(const props = data),全局变量变量data会自动注入,请不要进行声明。例如:
export default App() {
const props = data;
...
}
`,
},
{
role: 'assistant',
content: `好的我明白啦,请问你的业务是什么?`,
},
{
role: 'user',
content: `这是我传入的props:${JSON.stringify(
params.props.slice(0, min([params.props.length, 4]))
)}。${params.need}`,
},
],
(responseText: string, cancel?: boolean) => {
let code;
try {
code = JSON.parse(get(JSON.parse(responseText), 'arguments')).reactCode;
} catch (e) {}
res({
data: {
code: code,
},
});
},
undefined,
undefined,
[
{
name: 'runReactCode',
description: '作为一个实时编译React组件代码的函数。',
parameters: {
type: 'object',
properties: {
reactCode: {
type: 'string',
description: '输出一段能在react-live运行的代码',
},
},
required: ['sql', 'queryName', 'queryDescription', 'simulation'],
},
},
],
undefined,
{ name: 'runReactCode' },
false
);
});
// return backendApi.post(
// '/openAi/api/reactLive',
// isArray(params.props)
// ? {
// ...params,
// props: params.props.slice(0, min([params.props.length, 4])),
// }
// : params,
// privateConfig
// );
}
static getViewFunction(params: { data: Record<string, any>; need: string }) {
return backendApi.post(
'/openAi/api/code',
isArray(params.data)
? {
...params,
data: params.data.slice(0, min([params.data.length, 4])),
}
: params,
privateConfig
);
}
static checkChatResult(messageList: ChatMessage[]) {
return backendApi.post('/openAi/api/checkQuery', { messageList }, privateConfig);
}
}
'use client';
import { set } from 'lodash';
import axios from 'redaxios';
const APi = axios.create({});
export const backendApi = axios.create({
baseURL:globalThis?.sessionStorage?.getItem("baseURL") || '/backend',
});
(() => {
if (globalThis.sessionStorage && !sessionStorage.getItem("baseURL")) {
const url = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:3001'
axios.get((url) + "/schema/all").then(
() => {
set(backendApi.defaults, "baseURL", url)
sessionStorage.setItem("baseURL", url)
},
err => {
console.log(err);
sessionStorage.setItem("baseURL",'/backend')
}
);
}
})();
export default APi;
import getConfig from 'next/config';
import { backendApi } from '.';
import { getModel } from '@/utils/gpt';
import { fetchEventSource, EventStreamContentType } from '@microsoft/fetch-event-source';
import { MutableRefObject, SetStateAction } from 'react';
import { functions, get } from 'lodash';
export interface View {
type: 'schema' | 'table';
name?: string;
}
const model = getModel(process.env.NEXT_PUBLIC_MODEL_NAME || 'gpt-4o-mini');
export function prettyObject(msg: any) {
const obj = msg;
if (typeof msg !== 'string') {
msg = JSON.stringify(msg, null, ' ');
}
if (msg === '{}') {
return obj.toString();
}
if (msg.startsWith('```json')) {
return msg;
}
return ['```json', msg, '```'].join('\n');
}
type message = {
role: string;
content: string;
};
export default class OpenAI {
static async request(
messages: message[],
onFinish: (responseText: string, cancel?: boolean) => any,
onUpdate?: (responseText: string, delta: string) => any,
onError?: (e: Error) => any,
functions?: any[],
closeFn?: MutableRefObject<any>,
function_call?: any,
stream: boolean = true
) {
const requestPayload = {
messages: messages,
model: model.name,
temperature: model.temperature,
frequency_penalty: model.frequency_penalty,
presence_penalty: model.presence_penalty,
stream,
functions,
function_call,
};
const controller = new AbortController();
const chatPayload = {
method: 'POST',
body: JSON.stringify(requestPayload),
signal: controller.signal,
headers: {
'cache-control': 'no-cache',
'Content-Type': 'application/json',
'x-requested-with': 'XMLHttpRequest',
Accept: 'text/event-stream',
},
};
const requestTimeoutId = setTimeout(() => controller.abort(), 1000 * 120);
const chatPath = '/openai/v1/chat/completions';
if (closeFn) {
controller.signal.onabort = () => {
onFinish('', true);
};
closeFn.current = () => {
clearTimeout(requestTimeoutId);
controller.abort();
};
}
if (stream) {
let responseText = '';
let finished = false;
const finish = (cancel?: boolean) => {
if (!finished) {
onFinish(responseText, cancel);
finished = true;
}
};
controller.signal.onabort = () => {
finish(true);
};
fetchEventSource(chatPath, {
...chatPayload,
async onopen(res) {
clearTimeout(requestTimeoutId);
const contentType = res.headers.get('content-type');
// console.log('[OpenAI] request response content type: ', contentType);
if (contentType?.startsWith('text/plain')) {
responseText = await res.clone().text();
return finish();
}
if (
!res.ok ||
!res.headers.get('content-type')?.startsWith(EventStreamContentType) ||
res.status !== 200
) {
const responseTexts = [responseText];
let extraInfo = await res.clone().text();
try {
const resJson = await res.clone().json();
extraInfo = prettyObject(resJson);
} catch {}
if (extraInfo) {
responseTexts.push(extraInfo);
}
responseText = responseTexts.join('\n\n');
return finish();
}
},
onmessage(msg) {
if (msg.data === '[DONE]' || finished) {
return finish();
}
const text = msg.data;
try {
// console.log(text);
const json = JSON.parse(text);
const delta = json.choices[0].delta.content;
if (json.choices[0].finish_reason === 'function_call') {
// 🌟 output the Tree structure data
console.log(json);
}
if (delta) {
responseText += delta;
onUpdate?.(responseText, delta);
}
} catch (e) {
console.error('[Request] parse error', text, msg);
}
},
onclose() {
finish();
},
onerror(e) {
onError?.(e);
throw e;
},
openWhenHidden: true,
});
} else {
const res = await fetch(chatPath, chatPayload);
clearTimeout(requestTimeoutId);
const resJson = await res.json();
const message =
resJson.choices?.at(0)?.message?.content ??
(JSON.stringify(get(resJson, 'choices[0].message.function_call')) || '');
onFinish(message);
}
}
}
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,
Alert,
Switch,
} from '@arco-design/web-react';
import { useTranslation } from 'react-i18next';
import MessageItem from './MessageItem';
import { debounce, filter, get, map } from 'lodash';
import React, {
ReactElement,
ReactNode,
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react';
import { IconRecord, IconRobot, IconSend, IconSwap, IconVoice } from '@arco-design/web-react/icon';
import OpenAI from '@/client/api/openAI';
import getView from '@/client/api/getView';
let useSpeechToText: any;
export interface ChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
const getQuestion = (question: string) => `${question}`;
type AIProps = {
context?: any;
messageList: any[];
setMessageList: (value: any[]) => any;
startView?: number;
renderMessageItem?: any;
doneFx?: (message: string) => any;
simpleMode?: string | true;
simpleModeVisible?: boolean;
setSimpleModeVisible?: (flag?: boolean) => any;
welcome?: ReactElement | string;
quickTip?:
| { value: string | string[]; name: string | string[]; [key: string]: any }[]
| string[];
searchFlag?: RegExp;
inputProps?: Record<string, any>;
SendButton?: ({ inputRef }: { inputRef: any }) => JSX.Element;
noHistory?: boolean;
functions?: any[];
stream?: boolean;
function_call?: any;
isCheck?: boolean;
};
export function AIWrapper({
context,
messageList,
setMessageList,
startView = 0,
renderMessageItem,
doneFx,
simpleMode,
simpleModeVisible = false,
setSimpleModeVisible = () => {},
welcome = '',
quickTip = [],
searchFlag = /^\//,
inputProps = {},
SendButton,
noHistory,
functions,
function_call,
stream,
isCheck = false,
}: AIProps) {
const input = useRef<any>();
const scrollContainer = useRef<any>();
const [loading, setLoading] = useState(false);
// 发送信息事件和成功的事件
const [currentAssistantMessage, setCurrentAssistantMessage] = useState('');
const [thinking, setThinking] = useState(false); //假的加载
const [check, setCheck] = useState<string | boolean>(false); // 检查需求
const [checkOpen, setIsCheck] = useState(isCheck);
const handleButtonClickSuccess = useEffect(() => {
if (!currentAssistantMessage || loading) {
return;
}
setMessageList([
...messageList,
{
role: 'assistant',
content: currentAssistantMessage,
},
]);
setCurrentAssistantMessage('');
toView();
// return clearContext;
}, [currentAssistantMessage, loading]);
const closeRef = useRef<any>();
const close = () => {
if (closeRef.current) {
closeRef.current();
}
};
const toView = useCallback(
debounce(
() => {
scrollContainer &&
scrollContainer?.current?.scrollIntoView({
behavior: 'smooth',
block: 'end',
});
},
50,
{ maxWait: 500 }
),
[]
);
const requestMessageList = useMemo(() => {
if (functions) {
return messageList.map(v => {
if (v.role === 'assistant') {
let fn;
try {
fn = JSON.parse(get(v, 'content'));
} catch (e) {
fn = '';
}
return {
...v,
content: functions && fn ? '' : get(v, 'content'),
function_call: functions && fn ? fn : undefined,
};
}
return v;
});
}
return messageList;
}, [messageList]);
const handleButtonClick = useCallback(
(message?: string, callBack?: (m: string) => void) => {
const inputRef = input.current?.dom;
const inputValue = message || inputRef.value;
if (!inputValue) {
return;
}
let initMessageList = [
...requestMessageList,
{
role: 'user',
content: inputValue,
},
];
setMessageList([
...messageList,
{
role: 'user',
content: inputValue,
},
]);
// @ts-ignore
setRecordValue('');
setLoading(true);
toView();
let checked = checkOpen ? false : true;
const request = () => {
OpenAI.request(
noHistory
? initMessageList.slice(0, startView).concat({
role: 'user',
content: inputValue,
})
: initMessageList,
(currentAssistantMessageStr, cancel) => {
setThinking(true);
setCheck(true);
const done = () => {
setCheck(false);
setLoading(false);
setTimeout(toView, 100);
setCurrentAssistantMessage(currentAssistantMessageStr);
callBack && callBack(currentAssistantMessageStr);
doneFx && doneFx(currentAssistantMessageStr);
};
checked || cancel
? done()
: getView
.checkChatResult([
...initMessageList,
{
role: 'user',
content: inputValue,
},
{
role: 'assistant',
function_call: functions
? JSON.parse(currentAssistantMessageStr)
: undefined,
content: functions ? '' : currentAssistantMessageStr,
},
])
.then((v: any) => {
const { answerMeetsRequirements, why } = get(
v,
'data.output',
{}
);
checked = true;
setThinking(false);
setTimeout(toView, 100);
if (answerMeetsRequirements) {
done();
} else {
setCheck(why);
initMessageList.push(
{
role: 'assistant',
function_call: functions
? JSON.parse(currentAssistantMessageStr)
: undefined,
content: functions
? ''
: currentAssistantMessageStr,
},
{
role: 'user',
content: `错误!!!!,你的回答不符合的需求,你的回答存在以下问题:\n${why}。\n请改正这些问题并重新输出。`,
}
);
request();
}
});
},
val => {
setCurrentAssistantMessage(val);
toView();
},
() => {
Notification.error({
title: 'No Response',
content: undefined,
});
},
functions,
closeRef,
function_call,
stream || !functions
);
};
request();
},
[
loading,
messageList,
currentAssistantMessage,
setCurrentAssistantMessage,
input,
handleButtonClickSuccess,
scrollContainer,
setCheck,
checkOpen,
]
);
// 清除功能
const clear = () => {
const inputRef = get(input, 'current.dom');
if (inputRef) {
setRecordValue('');
setMessageList(messageList.slice(0, startView));
setCurrentAssistantMessage('');
}
};
// 用户信息
const [Niche, setNiche] = useState('you');
const user: {
photoId?: string;
} = {};
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 [isSearching, setSearching] = useState<string | false>(false);
const quickTipData = useMemo(() => {
const el = input.current?.dom;
if (isSearching && el) {
return filter(quickTip, ({ value }: { value: string }) => {
if (el) {
return value.indexOf(isSearching.replace(searchFlag, '').trim()) !== -1;
}
});
}
return [];
}, [isSearching, quickTip, input]);
//动画
const { t } = useTranslation('actions');
const animaeString = t(
!quickTip.length
? 'Please input your question'
: 'Starting with "/", retrieve frequently asked questions'
);
const [placeholder, setPlaceholder] = useState('');
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;
console.log(recordValue);
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 RenderMessageItem = renderMessageItem || MessageItem;
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={() => !SendButton && handleButtonClick()}
value={inputValue}
onChange={value => {
console.log(value, '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}
/>
);
const detail = useMemo<ReactNode>(() => {
if (check) {
return (
<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('Reviewing and checking if query aligns with requirements')}
<span>
<IconSwap />
</span>
</div>
{check !== true ? (
<Alert
content={t('Requirements and answers do not align')}
type={'warning'}
className="my-[20px]"
/>
) : null}
<pre
className="break-before-all"
style={{
whiteSpace: 'break-spaces',
}}
>
{check}
</pre>
</Form>
</div>
</div>
);
}
if (currentAssistantMessage) {
return (
<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>
);
} else {
return (
<div className="my-[20px]">
<Skeleton
animation
text={{
rows: 5,
width: ['100%', '100%', '100%', '100%', 400],
}}
/>
</div>
);
}
}, [currentAssistantMessage, check]);
console.log(currentAssistantMessage, 'currentAssistantMessage');
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={detail}
/>
)}
<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">
{check
? check !== true
? `${t('Requirements and answers do not align, rethinking')}...`
: `${t('Reviewing requirements and answers')}...`
: `${t('Analyzing requirements')}...`}
</div>
</div>
<Button className="shadow" onClick={close} size="mini" shape="round">
{t('Cancel')}
</Button>
</>
) : (
<Comment
className="items-stretch !mt-[5px] pt-[15px] mx-[-15px] px-[15px] shadow-t"
actions={[
<span className="flex items-center">
<span className="mr-[10px] text-[12px]">自动检查</span>
<Switch
type="line"
size="small"
checked={checkOpen}
onChange={value => {
setIsCheck(value);
}}
/>
</span>,
<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 => {
const el = input.current?.dom;
if (el) el.value = 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 [initNun, refresh] = useReducer(state => ++state, 0);
useEffect(() => {
if (window && !useSpeechToText) {
import('react-hook-speech-to-text').then(({ default: fx }) => {
useSpeechToText = fx;
refresh();
});
}
}, []);
return initNun || useSpeechToText ? <AIWrapper {...props} /> : null;
}
import { useCallback } from 'react';
import ReactFlow, {
MiniMap,
Controls,
Background,
useNodesState,
useEdgesState,
addEdge,
} from 'reactflow';
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 { Upload, Message, UploadProps } from '@arco-design/web-react';
import * as XLSX from 'xlsx';
import { useState } from 'react';
import pinyin from 'pinyin';
import { flatten, get, isDate, isString, map } from 'lodash';
const e = /^\s+$/g;
function getDataType(value: string) {
if (!value || (isString(value) && e.test(value))) {
console.log(value, 'value');
return 'VARCHAR(255)';
} else if (isDate(value) && !isNaN(Date.parse(value))) {
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
return 'DATE';
} else {
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d{1,3})?$/.test(value)) {
return 'DATETIME';
}
return 'VARCHAR(255)';
}
} else if (isFinite(Number(value))) {
const number = Number(value);
if (Number.isInteger(number)) {
if (number >= -2147483648 && number <= 2147483647) {
return 'INT';
} else {
return 'BIGINT';
}
} else {
if (number >= -9999999 && number <= 9999999) {
return 'DECIMAL(10, 2)';
} else {
return 'DECIMAL(20, 2)';
}
}
} else if (value === 'true' || value === 'false') {
return 'BOOLEAN';
} else {
if (value.length <= 255) {
return 'VARCHAR(255)';
} else {
return 'TEXT';
}
}
}
function isChinese(text: string) {
var re = /[\u4E00-\u9FA5\uF900-\uFA2D]/;
return re.test(text);
}
export function handleUpload(files: File[]) {
return new Promise(res => {
const ddlArr: string[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const reader = new FileReader();
reader.onload = (e: any) => {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, {
type: 'array',
cellDates: true,
cellNF: true,
});
const sheetName = workbook.SheetNames[0];
const jsonData = XLSX.utils.sheet_to_json<any>(workbook.Sheets[sheetName], {
defval: '',
header: 1,
});
let tableName = file.name.split('.')[0];
if (isChinese(tableName)) {
tableName = flatten(
pinyin(tableName, {
style: 'normal',
})
).join('_');
}
const columns: any[] = jsonData[0];
const columnTypes = map(columns, (_, column) => {
const firstNonNullValue = get(
jsonData.slice(1).find(row => row[column] != null),
[column],
'VARCHAR(255)'
);
return getDataType(firstNonNullValue);
});
const ddl = `CREATE TABLE \`${tableName}\` (${map(
columns,
(column, i) => `\`${column}\` ${columnTypes[i]}`
).join(', ')});`;
ddlArr.push(ddl);
if (ddlArr.length === files.length) {
res(ddlArr);
}
};
reader.readAsArrayBuffer((file as File & { originFile: File })?.originFile);
}
});
}
const ImportExcel = ({ setList }: { setList: typeof useState<UploadProps['fileList']> }) => {
return (
<Upload
drag
multiple
accept=".xls,.xlsx,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
onDrop={e => {
let uploadFile = e.dataTransfer.files;
}}
autoUpload={false}
onChange={lists => setList(lists)}
/>
);
};
export default ImportExcel;
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 { get, 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';
import ImportExcel, { handleUpload } from './importExcel';
const FormItem = Form.Item;
export function DBForm({
form,
slot = null,
initialValues = {
client: 'mysql2',
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '123789xyy',
database: 'chat_query',
},
}) {
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');
} else if (type === 4) {
console.log('excel');
setImportType('excel');
}
}, [type]);
const [val, setValue] = useState('');
const [importType, setImportType] = useState('dbml');
// eslint-disable-next-line react-hooks/exhaustive-deps
/**
* options: {
new: Boolean,
importType:string
}
*/
const handleOk = async (changeValue, handleCb, failCall, options = {}) => {
const value = changeValue || val;
console.log(lists, 'lists');
if (!value && !(lists.length && type === 4)) {
onCloseModal();
return;
}
try {
let result;
if (type === 4) {
const temp = await handleUpload(lists);
result = await Parser.parse(temp[0], 'mysql');
console.log(result, 'result');
} else {
result = await Parser.parse(value, get(options, 'importType', 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 => ({
...(options.new ? [] : state),
...tableDict,
}));
setLinkDict(state => ({
...(options.new ? [] : 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 [lists, setList] = useState([]);
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>
);
} else if (type === 4 || !type) {
result.push(
<TabPane key="excel" title="excel">
<ImportExcel setList={setList} />
</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 { Graph, Path } from '@antv/x6';
export default function ConnectorInit() {
Graph.registerConnector(
'algo-connector',
(s, e) => {
const offset = 4;
const deltaY = Math.abs(e.y - s.y);
const control = Math.floor((deltaY / 3) * 2);
const v1 = { x: s.x, y: s.y + offset + control };
const v2 = { x: e.x, y: e.y - offset - control };
return Path.normalize(
`M ${s.x} ${s.y}
L ${s.x} ${s.y + offset}
C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}
L ${e.x} ${e.y}
`
);
},
true
);
}
import { Cell } from '@antv/x6';
import { ReactShapeConfig } from '@antv/x6-react-shape';
import { type } from 'os';
import React from 'react';
interface DagContext {
activeCell?: Cell;
}
export enum DagActions {
'CHANGE_ACTIVE_CELL',
}
let count = 1;
type dispatchType<T> = (type: T) => any;
type ExtractProps<T> = T extends React.ComponentType<infer P> ? P : never;
type NodeProps = ReactShapeConfig['component'];
export type CustomProps = ExtractProps<NodeProps>;
export type DagContextType = React.Context<
DagContext & {
dispatch: (actions: {
type: DagActions;
payload: (typeof actions)['type'] extends DagActions.CHANGE_ACTIVE_CELL ? Cell : never;
}) => any;
}
>;
export default function getDagContext() {
const DagContext = React.createContext<DagContext>({});
DagContext.displayName = 'DagContext' + count;
const { Provider, Consumer, displayName } = DagContext;
console.log(count, 'countcount');
count++;
return { Provider, Consumer, displayName, DagContext };
}
import { Graph } from '@antv/x6';
export default function EdgesInit(){
Graph.registerEdge(
'dag-edge',
{
inherit: 'edge',
attrs: {
line: {
stroke: '#C2C8D5',
strokeWidth: 1,
targetMarker: null,
},
},
},
true
);
}
\ No newline at end of file
'use client';
import React, {
useEffect,
useRef,
useCallback,
useReducer,
type Provider as ReactProvider,
useMemo,
useState,
} from 'react';
import { Graph, Path, Cell } from '@antv/x6';
import { Selection } from '@antv/x6-plugin-selection';
// import insertCss from 'insert-css';
import { History } from '@antv/x6-plugin-history';
import { DagreLayout } from '@antv/layout';
import styled from 'styled-components';
import { Button, Collapse, Layout, List } from '@arco-design/web-react';
import { IconLayout } from '@arco-design/web-react/icon';
import { Dnd } from '@antv/x6-plugin-dnd';
import { MiniMap } from '@antv/x6-plugin-minimap';
import nodeStatusList from './nodeStatus';
import { NodeStatus, NodeType } from './nodes/AlgoNode';
import ConnectorInit from './connectors';
import EdgesInit from './edges';
import nodesInit from './nodes';
import { Div } from './styleWidgets';
import { SlideForm } from './nodeConfigs';
import getDagContext, { DagActions } from './context';
import { produce } from 'immer';
import { set, uniqueId } from 'lodash';
import { Portal } from '@antv/x6-react-shape';
const CollapseItem = Collapse.Item;
//注册
ConnectorInit();
EdgesInit();
nodesInit();
const Sider = Layout.Sider;
const Header = Layout.Header;
const Content = Layout.Content;
const X6ReactPortalProvider = Portal.getProvider();
function A() {
console.log('A');
return <div>A</div>;
}
function useFirst(fn: typeof getDagContext) {
const flag = useRef<ReturnType<typeof fn>>();
if (!flag.current) {
flag.current = fn();
}
return flag.current;
}
export default function Demo() {
const el = useRef<HTMLDivElement>(null);
const graphEl = useRef<Graph | null>();
const dndEl = useRef<Dnd | null>();
const context = useFirst(getDagContext);
const graph = graphEl.current;
const dnd = dndEl.current;
const { Provider, DagContext } = context;
console.log(DagContext, 'DagContext');
const [state, dispatch] = useReducer(
produce(
(
state: typeof Provider extends ReactProvider<infer P> ? P : never,
action: {
type: DagActions;
payload: (typeof action)['type'] extends DagActions.CHANGE_ACTIVE_CELL ? Cell : never;
}
) => {
switch (action.type) {
case DagActions.CHANGE_ACTIVE_CELL: {
console.info(action);
state.activeCell = action.payload;
}
}
}
),
{}
);
const store = useMemo(() => {
return {
...state,
dispatch: dispatch,
};
}, [state]);
console.log(store);
useEffect(() => {
if (el) {
const w = (el?.current?.clientWidth || 400) - 400;
const h = el?.current?.clientHeight;
const graph: Graph & {
DagContext?: typeof DagContext;
} = (graphEl.current = new Graph({
container: el.current!,
width: w,
height: 418,
panning: {
enabled: true,
eventTypes: ['leftMouseDown', 'mouseWheel'],
},
mousewheel: {
enabled: true,
modifiers: 'ctrl',
factor: 1.1,
maxScale: 1.5,
minScale: 0.5,
},
highlighting: {
magnetAdsorbed: {
name: 'stroke',
args: {
attrs: {
fill: '#fff',
stroke: '#31d0c6',
strokeWidth: 4,
},
},
},
},
connecting: {
snap: true,
allowBlank: false,
allowLoop: false,
highlight: true,
connector: 'algo-connector',
connectionPoint: 'anchor',
anchor: 'center',
validateMagnet({ magnet }) {
return magnet.getAttribute('port-group') !== 'top';
},
createEdge() {
return graph.createEdge({
shape: 'dag-edge',
attrs: {
line: {
strokeDasharray: '5 5',
},
},
zIndex: -1,
});
},
},
} as any));
graph.DagContext = DagContext;
const dnd = (dndEl.current = new Dnd({
target: graph,
}));
// graph.use(
// new MiniMap({
// container: document.getElementById('minimap'),
// }),
// )
graph.use(
new Selection({
multiple: true,
rubberEdge: true,
rubberNode: true,
modifiers: 'shift',
rubberband: true,
})
);
graph.use(
new History({
enabled: true,
})
);
graph.on('edge:connected', ({ edge }) => {
edge.attr({
line: {
strokeDasharray: '',
},
});
});
graph.on('node:change:data', ({ node }) => {
const edges = graph.getIncomingEdges(node);
const { status } = node.getData() as NodeStatus;
edges?.forEach(edge => {
if (status === 'running') {
edge.attr('line/strokeDasharray', 5);
edge.attr('line/style/animation', 'running-line 30s infinite linear');
} else {
edge.attr('line/strokeDasharray', '');
edge.attr('line/style/animation', '');
}
});
});
graph.on('node:contextmenu', ({ node, view }) => {
dispatch({
type: DagActions.CHANGE_ACTIVE_CELL,
payload: node,
});
});
// 初始化节点/边
const init = (data: Cell.Metadata[]) => {
const cells: Cell[] = [];
data.forEach(item => {
cells.push(graph.createNode(item));
});
graph.resetCells(cells);
};
init([
{
id: 'flow' + '_' + Date.now(),
shape: 'dag-node',
x: 290,
y: 110,
data: {
label: '流程开始',
status: 'success',
nodeType: NodeType.FLOW_START,
},
ports: [],
},
]);
graph.centerContent();
return () => {
graph.dispose();
};
}
}, []);
const startDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
// 该 node 为拖拽的节点,默认也是放置到画布上的节点,可以自定义任何属性
if (graph && dnd) {
const node = graph.createNode({
shape: 'rect',
width: 100,
height: 40,
});
dnd.start(node, e.nativeEvent);
}
};
return (
<Div className="react-portal-app">
<Layout className="h-[500px]">
<Header className="h-[40px] flex pl-[200px] bg-slate-50 border-b border-b-gray-200 relative z-[1] bg-clip-content">
<div className="px-[10px]">
<Button
iconOnly
icon={<IconLayout />}
onClick={() => {
// const dagreLayout = new DagreLayout({
// type: 'dagre',
// rankdir: 'LR',
// align: 'UR',
// ranksep: 35,
// nodesep: 15,
// });
// const model = dagreLayout.layout({
// nodes: graphEl.current?.getNodes().map((v,i) => {
// return {
// id: `${v.id}`,
// shape: 'circle',
// width: 32,
// height: 32,
// label: i,
// attrs: {
// body: {
// fill: '#5F95FF',
// stroke: 'transparent',
// },
// label: {
// fill: '#ffffff',
// },
// },
// };
// }),
// edges: graphEl.current?.getEdges(),
// });
// console.log(model);
// graphEl.current?.fromJSON(model);
}}
/>
</div>
</Header>
<Layout>
<Sider className="bg-slate-100 mt-[-40px] border-r border-r-gray-200 pt-[40px]">
<Collapse bordered={false} defaultActiveKey={['1']}>
<CollapseItem header="Query" name="1">
<List
style={{ width: '100%' }}
size="small"
header={null}
dataSource={['查询1', '查询2']}
render={(item, index) => (
<div className="m-[10px] text-center bg-slate-300 p-[5px] rounded cursor-pointer text-purple-400 font-bold shadow">
<span className="w-full" key={index}>
{item}
</span>
</div>
)}
/>
</CollapseItem>
</Collapse>
</Sider>
<Content>
<Provider value={store}>
<X6ReactPortalProvider />
</Provider>
<div ref={el}></div>
</Content>
<Sider
className="bg-slate-100 mt-[-40px] border-l border-l-gray-200 pt-[40px]"
collapsible
collapsedWidth={0}
>
<SlideForm />
</Sider>
</Layout>
</Layout>
</Div>
);
}
import { Button, SchemaForm } from 'formily-arco';
import { useCallback, useRef } from 'react';
const schema = {
type: 'object',
properties: {
select: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{useAsyncDataSource(loadData)}}',
},
},
}
const scope = {
async loadData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
label: 'CCC',
value: 'ccc',
},
{
label: 'DDD',
value: 'ddd',
},
])
}, 1000)
})
},
}
export function SlideForm (){
const formRef = useRef<any>()
const handleClick = useCallback(() => {
console.log(formRef?.current?.getForm()?.values)
}, [])
return (
<div className="p-[10px] bg-slate-50 max-h-full h-full">
<SchemaForm ref={formRef} schema={schema} scope={scope} />
<Button onClick={handleClick}>点击测试</Button>
</div>
)
}
\ No newline at end of file
const nodeStatusList = [
[
{
id: '1',
status: 'running',
},
{
id: '2',
status: 'default',
},
{
id: '3',
status: 'default',
},
{
id: '4',
status: 'default',
},
],
[
{
id: '1',
status: 'success',
},
{
id: '2',
status: 'running',
},
{
id: '3',
status: 'default',
},
{
id: '4',
status: 'default',
},
],
[
{
id: '1',
status: 'success',
},
{
id: '2',
status: 'success',
},
{
id: '3',
status: 'running',
},
{
id: '4',
status: 'running',
},
],
[
{
id: '1',
status: 'success',
},
{
id: '2',
status: 'success',
},
{
id: '3',
status: 'success',
},
{
id: '4',
status: 'failed',
},
],
];
export default nodeStatusList;
\ No newline at end of file
import { get, map, uniqueId } from 'lodash';
import { useContext, useEffect, useMemo, useState } from 'react';
import { CustomProps, DagContextType } from '../context';
import {
Button,
Dropdown,
Form,
Input,
Menu,
Modal,
Popover,
Select,
Trigger,
} from '@arco-design/web-react';
import { IconDelete, IconPlus } from '@arco-design/web-react/icon';
import { Graph, Node } from '@antv/x6';
import { DagreLayout } from '@antv/layout';
export interface NodeStatus {
id: string;
status: 'default' | 'success' | 'failed' | 'running';
label?: string;
nodeType?: NodeType;
}
export enum NodeType {
'FLOW_START',
}
const image = {
logo: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*evDjT5vjkX0AAAAAAAAAAAAAARQnAQ',
success: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*6l60T6h8TTQAAAAAAAAAAAAAARQnAQ',
failed: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*SEISQ6My-HoAAAAAAAAAAAAAARQnAQ',
running: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*t8fURKfgSOgAAAAAAAAAAAAAARQnAQ',
};
function removeNodeAndConnected(node: Node, graph: Graph) {
// 获取与节点关联的所有边
const connectedEdges = graph.getConnectedEdges(node, { deep: true });
connectedEdges.forEach(edge => {
// 找出这个节点作为source的目标节点
if (edge.getSourceCell() === node) {
const target = edge.getTargetCell() as Node;
if (target) {
// 递归删除目标节点及其关联的节点和边
removeNodeAndConnected(target, graph);
}
}
// 删除边
graph.removeCell(edge);
});
// 删除节点
graph.removeCell(node);
}
const FormItem = Form.Item;
const Option = Select.Option;
const data = {
函数: ['Chengdu', 'Mianyang', 'Aba'],
'SQL 查询': ['Beijing', 'Shanghai', 'Tianjin'],
算子: ["Xi'an", "Xi'an", "Xi'an"],
};
function MenuList({ node, graph, nodeType }: { node: Node; graph: Graph; nodeType: NodeType }) {
const types = Object.keys(data) as Array<keyof typeof data>;
const defaultType = types[0];
const [type, setType] = useState<keyof typeof data>(defaultType);
const [cities, setCities] = useState<string[]>([]);
const [city, setCity] = useState('');
useEffect(() => {
const cities = data[type] || [];
setCities(cities);
setCity(cities[0]);
}, [type]);
return (
<div className="w-[150px] min-h-[50px]">
<Button
type="default"
className="cursor-pointer w-full text-left my-[5px]"
icon={<IconPlus />}
onClick={() => {
Modal.confirm({
title: '配置节点',
icon: null,
footer: null,
content: (
<div>
<Form autoComplete="off">
<FormItem label="函数类型">
<Select
placeholder="Select Province"
onChange={value => setType(value)}
defaultValue={type}
>
{map(data, (_, k) => {
console.log(k, 'kkk');
return (
<Option key={k} value={k}>
{k}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="函数名称">
<Select
placeholder="Select city"
onChange={value => setCity(value)}
value={city}
></Select>
</FormItem>
<FormItem wrapperCol={{ offset: 5 }}>
<Button type="primary">确定</Button>
</FormItem>
</Form>
</div>
),
});
const NeedPorts = [
{
id: node.id + 'port' + uniqueId(),
group: 'bottom',
},
{
id: node.id + 'port' + uniqueId(),
group: 'top',
},
];
const childNode = {
id: node.id + '_' + 'node' + uniqueId(),
shape: 'dag-node',
x: 0,
y: 0,
data: {
label: '读数据',
status: 'success',
},
ports: [NeedPorts[1]],
};
node.addPort(NeedPorts[0]);
setTimeout(() => {
graph.addNode(childNode);
graph.addEdge({
id: node.id + '_' + 'edge' + uniqueId(),
shape: 'dag-edge',
source: {
cell: node.id,
port: NeedPorts[0].id,
},
target: {
cell: childNode.id,
// port: NeedPorts[1].id,
},
zIndex: 0,
});
// 获取所有节点
const nodes = graph
.getNodes()
.map(node => ({ id: node.id, raw: node, ...node.size() }));
// 获取所有边
const edges = graph.getEdges().map(edge => ({
source: edge.getSourceCell()!.id,
target: edge.getTargetCell()!.id,
raw: edge,
}));
const dagreLayout = new DagreLayout({
type: 'dagre',
rankdir: 'TB',
ranksep: 30,
nodesep: 70,
});
const model = dagreLayout.layout({
nodes,
edges,
}) as {
nodes: ((typeof nodes)[0] & { x: number; y: number })[];
edges: typeof edges;
};
(model.nodes || []).forEach(({ raw, x, y }) => {
raw.position(x, y);
});
graph.centerContent();
}, 50);
}}
>
添加子节点
</Button>
<Button
hidden={nodeType === NodeType.FLOW_START}
type="default"
status="danger"
className="cursor-pointer w-full text-left my-[5px]"
icon={<IconDelete />}
onClick={() => {
removeNodeAndConnected(node, graph);
}}
>
删除
</Button>
</div>
);
}
const AlgoNode = (props: CustomProps) => {
const { node, graph } = props;
const data = node?.getData() as NodeStatus;
const { label, status = 'default', nodeType } = data;
const DagContext: DagContextType = get(graph, 'DagContext')!;
const { activeCell } = useContext(DagContext);
graph.removeConnectedEdges;
function render() {
if (nodeType === NodeType.FLOW_START) {
return (
<Button type="outline" className="w-full" shape="round" status="success">
流程开始
</Button>
);
} else {
return (
<div className={`node ${status}`}>
<img src={image.logo} alt="logo" />
<span className="label">{label}</span>
<span className="status">
{status === 'success' && <img src={image.success} alt="success" />}
{status === 'failed' && <img src={image.failed} alt="failed" />}
{status === 'running' && <img src={image.running} alt="running" />}
</span>
</div>
);
}
}
return (
<>
{render()}
<Popover
trigger="hover"
content={<MenuList node={node} graph={graph} nodeType={nodeType} />}
position="right"
title="操作"
>
<Button
hidden={activeCell?.id !== node?.id}
icon={<IconPlus />}
shape="circle"
iconOnly
type="primary"
className="inset-y-0 right-[-30px] m-auto absolute"
size="mini"
></Button>
</Popover>
</>
);
};
export default {
shape: 'dag-node',
width: 180,
height: 36,
component: AlgoNode,
ports: {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#C2C8D5',
strokeWidth: 1,
fill: '#fff',
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#C2C8D5',
strokeWidth: 1,
fill: '#fff',
},
},
},
},
},
};
import { Graph, Node } from '@antv/x6';
import { Button } from '@arco-design/web-react';
import { CustomProps } from '../context';
function StartNode(props: CustomProps) {
return <Button type="primary">流程开始</Button>;
}
export default {
shape: 'start-node',
width: 180,
height: 36,
component: StartNode,
ports: {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#C2C8D5',
strokeWidth: 1,
fill: '#fff',
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#C2C8D5',
strokeWidth: 1,
fill: '#fff',
},
},
},
},
},
};
import { register } from '@antv/x6-react-shape';
import AlgoNode from './AlgoNode';
export default function nodesInit() {
register(AlgoNode);
}
import styled from "styled-components";
export const Div = styled.div`
border: #c2c8d59d 1px solid;
margin: 5px;
.x6-graph {
height: 100% !important;
}
.node {
display: flex;
align-items: center;
width: 100%;
height: 100%;
background-color: #fff;
border: 1px solid #c2c8d5;
border-left: 4px solid #5f95ff;
border-radius: 4px;
box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06);
}
.node img {
width: 20px;
height: 20px;
flex-shrink: 0;
margin-left: 8px;
}
.node .label {
display: inline-block;
flex-shrink: 0;
width: 104px;
margin-left: 8px;
color: #666;
font-size: 12px;
}
.node .status {
flex-shrink: 0;
}
.node.success {
border-left: 4px solid #52c41a;
}
.node.failed {
border-left: 4px solid #ff4d4f;
}
.node.running .status img {
animation: spin 1s linear infinite;
}
.x6-node-selected .node {
border-color: #1890ff;
border-radius: 2px;
box-shadow: 0 0 0 4px #d4e8fe;
}
.x6-node-selected .node.success {
border-color: #52c41a;
border-radius: 2px;
box-shadow: 0 0 0 4px #ccecc0;
}
.x6-node-selected .node.failed {
border-color: #ff4d4f;
border-radius: 2px;
box-shadow: 0 0 0 4px #fedcdc;
}
.x6-edge:hover path:nth-child(2) {
stroke: #1890ff;
stroke-width: 1px;
}
.x6-edge-selected path:nth-child(2) {
stroke: #1890ff;
stroke-width: 1.5px !important;
}
.arco-collapse-item-content-box{
padding: 5px;
}
@keyframes running-line {
to {
stroke-dashoffset: -1000;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`;
\ No newline at end of file
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}`,
});
},
undefined,
{
new: true,
importType: 'dbml',
}
);
}}
// 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,
IconLayout,
IconLoading,
IconLoop,
} 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,
autoLayout,
}) {
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: '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>
<Button
size="small"
type="secondary"
status="default"
shape="circle"
className="shadow"
onClick={async () => {
setSaveLoading(true);
await autoLayout();
setSaveLoading(false);
}}
>
{!saveLoading ? <IconLoop /> : <IconLoading />}
</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', 4)}
>
<IconImport /> {t('Import excel')}
</Menu.Item>
<Menu.Item
key="import"
className="context-menu-item"
onClick={() => setShowModal('import', 2)}
>
<IconImport /> {t('Import DDL')}
</Menu.Item>
<Menu.Item
key="import"
className="context-menu-item"
onClick={() => setShowModal('import', 1)}
>
<IconImport /> {t('Import DBML')}
</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={2}
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(
`
${sql}
${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>
);
}
version: '3'
services:
mysql:
image: mysql:8.4
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=123789
- MYSQL_DATABASE=chat_query
- MYSQL_USER=xyy
- MYSQL_PASSWORD=123789
volumes:
- ./out:/var/lib/mysql
nest:
image: jadenxiong/chat-query-backend:fusionTeach
restart: on-failure
environment:
- OPEN_AI_API_KEY=${OPEN_AI_API_KEY}
- MODEL_NAME=gpt-4o-mini
- BASE_URL=https://open-ai-xyy.deno.dev/v1
- DB_HOST=mysql
- DB_PORT=3306
- DB_PASSWORD=123789
- DB_USER=root
ports:
- "3001:3001"
depends_on:
- mysql
next:
image: jadenxiong/chat-query:fusionTeach
environment:
- NEXT_PUBLIC_BACKEND_URL=http://nest:3001/
- NEXT_PUBLIC_OPEN_AI_API_KEY=${OPEN_AI_API_KEY}
- OPENAI_PROXY_URL=https://open-ai-xyy.deno.dev/
- MODEL_NAME=gpt-4o-mini
ports:
- "3000:3000"
depends_on:
- nest
/* 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;
export const QUERY_FUNCTION = [
{
name: 'saveExecuteSqlInfo',
description:
'Unique and mandatory execution (no other functions can be executed), storing the key information required to execute this requirement',
parameters: {
type: 'object',
properties: {
simulation: {
type: 'boolean',
description:
'This requirement needs you to generate simulated data, with true or false options.',
},
sql: {
type: 'string',
description: `If the user's requirements include generating simulated data, you should generate this data first and ensure that it is included in the SQL output.,The SQL statement required to execute the operation must adhere to standard syntax. Additionally, the data inserted must comply with the existing table structure.`,
},
variablesArr: {
type: 'array',
description:
'Execute SQL statement containing an array of variables and their descriptions: If the requirement specifies simulated data, the parameter should be null.',
items: {
type: 'object',
properties: {
variable: {
type: 'string',
description:
'The name of the variables.The information that needs to be filled in by the user should be treated as variables, and the variable names must be enclosed between $ signs.',
},
varDescription: {
type: 'string',
description: 'The description of the variable',
},
},
required: ['variable', 'varDescription'],
},
},
queryName: {
type: 'string',
description: 'The name of the query',
},
queryDescription: {
type: 'string',
description: 'The description of the query',
},
},
required: ['sql', 'queryName', 'queryDescription', 'simulation'],
},
},
];
type dbml = string;
export const GET_QUERY = (
sql: dbml
) => `作为一个数据库模型业务分析专家,您需要根据当前数据库模型(DBML格式)生成对应的 MySQL 数据库可执行的 SQL 文本,并列出执行该 SQL 所需的变量及其解释,最后为此条查询命名。当前数据库模型为:
\`\`\`dbml
${sql}
\`\`\`
请将以下中的xml标签中的内容进行分析(用户需要填入的信息作为变量,如果需要变量:变量的命名必须请放在$和$之间,例如:$tab$、$name$...)。:
根据您提供的数据库模型,已为您生成查询:
<sql>按照需要生产的sql语句</sql>
执行所需变量:
判断解决当前问题的查询执行时是否需要变量。若不需要变量,则无需提供任何信息。 若条件成立,请使用:
<var>$执行SQL所需变量名称$</var>: <varDescription>变量解释</varDescription>
查询命名和描述:
<queryName>查询名称</queryName>
<queryDescription>查询描述</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>
`;
// chat to query
export const CLEAR_ALL_TABLES_DATA = '重置表中所有数据,包括重置主键';
export const QUERY_ALL_DATA = '查询所有表中的数据';
export const LIST_ALL_TABLES = '查出所有的表';
export const CHINESE_DATA_POPULATOR =
'分析模型表之间的关联关系,不提供参数的情况下,在数据库每个表中插入数量不等的生产环境标准的中文模拟数据';
// chat to view
export const chatToView = ['表格', '图表', '卡片', '表单', '列表'];
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 { Application, proxy } from 'https://deno.land/x/oak/mod.ts';
const app = new Application();
app.use(proxy('https://api.openai.com'));
app.listen({ port: 8000 });
# 使用 Node.js 14 作为基础镜像
FROM node:18
# 设置工作目录
WORKDIR /app
# 复制整个应用到容器中
COPY ./ /app
ARG NEXT_PUBLIC_BACKEND_URL
ENV NEXT_PUBLIC_BACKEND_URL $NEXT_PUBLIC_BACKEND_URL
ARG NEXT_PUBLIC_OPEN_AI_API_KEY
ENV NEXT_PUBLIC_OPEN_AI_API_KEY $NEXT_PUBLIC_OPEN_AI_API_KEY
ARG OPENAI_PROXY_URL
ENV OPENAI_PROXY_URL $OPENAI_PROXY_URL
# 安装依赖
RUN npm i -g pnpm
RUN pnpm i
# 暴露 3000 端口
EXPOSE 3000
# 启动应用
CMD ["sh", "-c", "pnpm build && pnpm start"]
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 { NextRequest, NextResponse } from 'next/server';
const url = new URL(`${process.env.OPENAI_PROXY_URL || 'https://api.openai.com'}`);
const PROTOCOL = url.protocol;
const BASE_URL = url.host;
const DISABLE_GPT4 = !!process.env.DISABLE_GPT4;
console.log(PROTOCOL, BASE_URL);
export async function requestOpenai(req: NextRequest) {
const controller = new AbortController();
const authValue = `Bearer ${process.env.NEXT_PUBLIC_OPEN_AI_API_KEY || ''}`;
const openaiPath = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll('/openai/', '');
let baseUrl = BASE_URL;
if (!baseUrl.startsWith('http')) {
baseUrl = `${PROTOCOL}//${baseUrl}`;
}
console.log('[Proxy] ', openaiPath);
console.log('[Base Url]', baseUrl);
if (process.env.OPENAI_ORG_ID) {
console.log('[Org ID]', process.env.OPENAI_ORG_ID);
}
const timeoutId = setTimeout(() => {
controller.abort();
}, 10 * 60 * 1000);
const fetchUrl = `${baseUrl}/${openaiPath}`;
const fetchOptions: RequestInit = {
headers: {
'Content-Type': 'application/json',
Authorization: authValue,
...(process.env.OPENAI_ORG_ID && {
'OpenAI-Organization': process.env.OPENAI_ORG_ID,
}),
},
cache: 'no-store',
method: req.method,
body: req.body,
// @ts-ignore
duplex: 'half',
signal: controller.signal,
};
// #1815 try to refuse gpt4 request
if (DISABLE_GPT4 && req.body) {
try {
const clonedBody = await req.text();
fetchOptions.body = clonedBody;
const jsonBody = JSON.parse(clonedBody);
if ((jsonBody?.model ?? '').includes('gpt-4')) {
return NextResponse.json(
{
error: true,
message: 'you are not allowed to use gpt-4 model',
},
{
status: 403,
}
);
}
} catch (e) {
console.error('[OpenAI] gpt4 filter', e);
}
}
try {
const res = await fetch(fetchUrl, fetchOptions);
// to prevent browser prompt for credentials
const newHeaders = new Headers(res.headers);
newHeaders.delete('www-authenticate');
// to disbale ngnix buffering
newHeaders.set('X-Accel-Buffering', 'no');
return new Response(res.body, {
status: res.status,
statusText: res.statusText,
headers: res.headers,
});
} finally {
clearTimeout(timeoutId);
}
}
export async function middleware(request: NextRequest) {
// Only process requests that start with /openai/
console.log(new URL(request.url).pathname, 'targetUrl');
const path = new URL(request.url).pathname;
if (!path.startsWith('/openai/')) {
return NextResponse.next();
}
try {
return await requestOpenai(request);
} catch (e) {
console.log(e);
}
}
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: {
'Sample data generated success': '示例数据生成成功',
'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',
'Import excel': '导入excel',
'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: {
'Import DBML': '导入 DBML',
'Import DDL': '导入 DDL',
'Import excel': '导入excel',
'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': '创建查询中',
'Query generation in progress': '正在生成查询',
'Reviewing and checking if query aligns with requirements':
'正在审核查询是否满足需求',
'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': '错误查询',
Cancel: '取消',
'Cannot generate executable query from the requirement description, please continue to describe the requirement!':
'从需求描述中不能为您生成可执行的查询,请继续描述的需求!',
'Requirements and answers do not align': '需求和回答没有对齐',
'Requirements and answers do not align, rethinking':
'需求和回答没有对齐,重新思考中',
'Reviewing requirements and answers': '正在审查需求和回答',
},
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: '名称',
},
chatView: {
'custom display': '自定义显示',
'Function for processing data': '处理数据的函数',
'Add component': '添加组件',
'Code generation in progress, please wait...': '代码生成中,请稍后...',
'Data processing functions': '数据处理函数',
'Comment + Tab to generate function': '以 `//` 开头的注释 + Tab 生成函数',
'Execute function name': '执行函数名称',
'No function description available': '暂无函数描述',
Run: '运行',
'Execution successful': '执行成功',
'Loading dependencies': '正在加载依赖',
'Loading completed': '加载完成',
},
},
},
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 withOffline = require('next-offline');
const path = require('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 = {
transpilePackages: ['@formilyjs/arco'],
reactStrictMode: true,
publicRuntimeConfig: {
apiPath: '/api/',
backendOrigin: process.env.NEXT_PUBLIC_BACKEND_URL,
},
rewrites,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
generateSw: false,
// dontAutoRegisterSw: true,
generateInDevMode: true,
workboxOpts: {
swSrc: './serviceWorker.js',
mode: process.env.NODE_ENV,
swDest: path.resolve(__dirname, './public/service-worker.js'),
maximumFileSizeToCacheInBytes: 7 * 1024 * 1024,
include: [
_ => {
if (/^static\//.test(_.asset.name) && !/\[.*\]/.test(_.asset.name))
console.log(_.asset.name, '333');
return /^static\//.test(_.asset.name) && !/\[.*\]/.test(_.asset.name);
},
],
},
experimental: {
proxyTimeout: 1000 * 120,
},
};
module.exports = withOffline(nextConfig);
{
"name": "v-query",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start --keepAliveTimeout 70000",
"lint": "next lint"
},
"dependencies": {
"@antv/layout": "^0.1.24",
"@antv/x6": "^2.0.0",
"@antv/x6-plugin-clipboard": "^2.0.0",
"@antv/x6-plugin-dnd": "^2.1.1",
"@antv/x6-plugin-export": "^2.0.0",
"@antv/x6-plugin-history": "^2.0.0",
"@antv/x6-plugin-keyboard": "^2.0.0",
"@antv/x6-plugin-minimap": "^2.0.0",
"@antv/x6-plugin-scroller": "^2.0.0",
"@antv/x6-plugin-selection": "^2.0.0",
"@antv/x6-plugin-snapline": "^2.0.0",
"@antv/x6-plugin-stencil": "^2.0.0",
"@antv/x6-plugin-transform": "^2.0.0",
"@antv/x6-react-components": "^2.0.0",
"@antv/x6-react-shape": "^2.0.0",
"@antv/x6-vue-shape": "^2.0.0",
"@arco-design/web-react": "^2.45.0",
"@dbml/core": "^2.5.1",
"@digitalocean/do-markdownit": "^1.9.0",
"@formily/core": "^2.2.29",
"@formily/react": "^2.2.29",
"@koale/useworker": "^4.0.2",
"@microsoft/fetch-event-source": "^2.0.1",
"@monaco-editor/react": "^4.4.5",
"@next/font": "13.1.6",
"@types/lodash": "^4.14.191",
"@types/react-dom": "18.0.10",
"acorn": "^8.10.0",
"acorn-jsx": "^5.3.2",
"acorn-walk": "^8.2.0",
"autoprefixer": "^10.4.13",
"classnames": "^2.3.1",
"dagre": "^0.8.5",
"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",
"formily-arco": "^1.0.1-beta.13",
"i18next": "^22.5.0",
"i18next-browser-languagedetector": "^7.0.2",
"immer": "^10.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",
"next-offline": "^5.0.5",
"npm": "^9.8.1",
"pinyin": "3.0.0-alpha.5",
"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-json-view": "^1.21.3",
"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",
"sucrase": "^3.32.0",
"swr": "^2.0.3",
"systemjs": "^6.14.1",
"tailwindcss": "^3.2.7",
"unstated-next": "^1.1.0",
"workbox-precaching": "^7.0.0",
"workbox-window": "^7.0.0",
"xlsx": "^0.18.5"
},
"resolutions": {
"postcss": "^8.4.14",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"postcss": "^8.4.14",
"postcss-nesting": "^11.3.0",
"typescript": "4.9.5"
},
"engines": {
"node": "18"
},
"browser": {
"fs": false,
"os": false,
"path": false
}
}
\ 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 } from '@arco-design/web-react';
import { useState } from 'react';
import { Spin } from '@arco-design/web-react';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
type MyAppProps = {
Component: React.FunctionComponent;
pageProps: Record<string, any>;
};
function MyApp({ Component, pageProps }: MyAppProps) {
const [local, setLocal] = useState<any>();
const { i18n } = useTranslation();
useEffect(() => {
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 {
Alert,
Button,
Card,
Collapse,
Input,
Select,
Spin,
Tag,
Tooltip,
} from '@arco-design/web-react';
import {
IconCode,
IconEye,
IconPlus,
IconRefresh,
IconSend,
IconSwap,
} from '@arco-design/web-react/icon';
import React, {
ReactElement,
memo,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
LiveEditor,
LivePreview,
LiveProvider,
useLiveContext,
useRunner,
} from 'react-live-runner';
import * as styledComponents from 'styled-components';
import * as echarts from 'echarts';
import ReactDOM from 'react-dom';
import 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, isArray, isEqual, map, set } from 'lodash';
import { useTranslation } from 'react-i18next';
import Editor, { Monaco, useMonaco, OnMount } from '@monaco-editor/react';
import { XML } from '@/utils/getXMLContent';
import { useWorker } from '@koale/useworker';
import dynamic from 'next/dynamic';
import { Parser } from 'acorn';
import ConnectDb, { Query } from '@/client/api/connectDb';
import 'systemjs';
import { simple } from 'acorn-walk';
import jsx from 'acorn-jsx';
import i18n from '@/next-i18next.config';
import { chatToView } from '@/data/prompt/query-tip';
const ReactJson = dynamic(() => import('react-json-view'), { ssr: false });
const CollapseItem = Collapse.Item;
type MonacoEditor = Parameters<OnMount>[0];
declare const System: any;
function Error() {
const { error } = useLiveContext();
console.log(error, 'error');
return <code>{error}</code>;
}
// const initText = i18n.getFixedT(i18n.language, 'chatView')('Function for processing data');
const init = (initText: string): string => {
return `/**
* @description ${initText}
* @param record<string,any> data
*/
`;
};
async function getUMDPath(packageName: string) {
const response = await fetch(`https://data.jsdelivr.com/v1/packages/npm/${packageName}`);
const packageJson = await response.json();
const packageLatest = get(packageJson, 'versions[0]');
const version = get(packageLatest, 'version');
const entryFile = await (await fetch(get(packageLatest, 'links.entrypoints'))).json();
// 一些包可能会在 "browser" 字段指定UMD模块的路径
return map(get(entryFile, 'entrypoints', []), (item: any, type: string) => {
return {
type: type,
url: `https://cdn.jsdelivr.net/npm/${packageName}@${version}${get(item, 'file')}`,
};
});
}
function loadUMDModule(packageName: string) {
window.React = React;
window.ReactDOM = ReactDOM;
return new Promise((resolve, reject) => {
try {
const script = document.createElement('script');
script.type = 'module';
const initFnName: any = [packageName + 'Init'];
window[initFnName] = mod => {
resolve(mod);
delete window[initFnName];
};
const errHandler = e => {
resolve(loadUMDModuleBySystem(packageName));
window.removeEventListener('error', errHandler);
console.log('error: esm load err', e);
};
window.addEventListener('error', errHandler);
script.textContent = `
import * as module from "https://cdn.jsdelivr.net/npm/${packageName}/+esm";
console.log(module,"module")
window["${initFnName}"] && window["${initFnName}"](module)
`;
document.head.append(script);
(async () => {
const umdPath = await getUMDPath(packageName);
for (const item of umdPath) {
if (item.type === 'css') {
System.import(item.url).then(function (module: any) {
const styleSheet = module.default; // A CSSStyleSheet object
console.log(styleSheet, 'styleSheet');
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
styleSheet,
]; // now your css is available to be used.
});
}
}
})();
} catch (e) {
console.log(e);
}
});
}
async function loadUMDModuleBySystem(packageName: string) {
try {
window.define = undefined;
window.React = React;
window.ReactDOM = ReactDOM;
const umdPath = await getUMDPath(packageName);
let mod;
for (const item of umdPath) {
const res = await System.import(item.url);
if (item.type === 'js') {
mod = res;
}
}
return mod;
} catch (error) {
console.error(error);
}
}
function ReactView({ props }: { props: Record<string, any> }) {
const { code } = useLiveContext();
const { t } = useTranslation('chatView');
return (
<div>
<div className="flex justify-end my-[5px]">
{window !== window.parent
? [
<Button
className="mr-[60px] shadow"
type="primary"
size="small"
shape="round"
onClick={() => {
if (window.parent) {
window.parent.postMessage(
{
code: code,
type: 'code',
props,
},
'*'
);
}
}}
>
<IconPlus /> {t('Add component')}
</Button>,
]
: null}
</div>
<LivePreview className="overflow-auto min-h-[350px]" />
</div>
);
}
export function ChatView({
defaultNode,
props: propsRaw,
id,
functions = '',
}: {
defaultNode?: ReactElement;
props: Record<string, any>;
id: string;
functions: Query['content']['functions'];
}) {
const [showCode, setShowCode] = useState(true);
const reqRef = useRef(nanoid());
const [showTable, setShowTable] = useState(false);
const [props, setProps] = useState(propsRaw);
const { t } = useTranslation('chatView');
const [dependenciesMap, setDepend] = useState({
react: React,
lodash: get(window, '_'),
'react-dom': ReactDOM,
'styled-components': styledComponents,
'echarts-for-react': ReactECharts,
echarts: {
default: echarts,
echarts: echarts,
...echarts,
},
});
useEffect(() => {
setProps(propsRaw);
}, [propsRaw]);
const [loadDependName, setLoadDependName] = useState<ReactElement | string>('');
const { trigger, data, isMutating } = useSWRMutation(
reqRef.current,
(_, { arg: { need } }) => {
return getView.getViewComponent({
props,
need,
});
},
{
async onSuccess(data) {
// loadUMDModule('echarts').then(mod => {
// console.log(mod, 'mod');
// });
const example = get(data, 'data.code');
const ast = Parser.extend(jsx()).parse(example, {
sourceType: 'module',
ecmaVersion: 6,
});
const dependencies: string[] = [];
try {
simple(ast, {
ImportDeclaration(node: any) {
node.source?.value && dependencies.push(node.source.value);
},
});
} catch (e) {}
const deps: any = {};
for (const depend of dependencies) {
if (!get(dependenciesMap, depend)) {
setLoadDependName(
<div>
{t('Loading dependencies')}:{' '}
<Tag bordered color="orange">
{depend}
</Tag>
</div>
);
await loadUMDModule(depend).then(mod => {
console.log(mod);
if (mod) {
deps[depend] = mod;
}
});
setLoadDependName(
<div>
{t('Loading completed')}:{' '}
<Tag bordered color="orange">
{depend}
</Tag>
</div>
);
}
}
setLoadDependName('');
setDepend({
...dependenciesMap,
...deps,
});
},
}
);
const [viewWidgetType, setViewWidgetType] = useState<string>('表格');
const selectEl = (
<Select value={viewWidgetType} className="widgetPicker" onChange={setViewWidgetType}>
<Select.Option value="0">
<span className={`${viewWidgetType === '0' ? 'text-[var(--pc)]' : ''}`}>
{t('custom display')}
</span>
</Select.Option>
{chatToView.map((v, i) => {
return (
<Select.Option value={v} key={v}>
<span className={`${viewWidgetType === v ? 'text-[var(--pc)]' : ''}`}>
{v}
</span>
</Select.Option>
);
})}
</Select>
);
const SendButton = useCallback(
({ inputRef }: { inputRef: any }) => {
return (
<Button
shape="circle"
icon={data ? <IconRefresh /> : <IconSend />}
size="mini"
type="primary"
loading={isMutating}
onClick={async () => {
switch (viewWidgetType) {
case '表单':
trigger({
need: `${inputRef.current.dom.value},请先分析数据,根据字段数据类型选择最合适的表单控件,并使用一个表单组件展示这些控件作为该数据的新增表单。每个控件应以字段名称(请翻译为中文)作为标签。`,
}).then(() => {
setShowTable(false);
});
return;
case '0':
trigger({
need: `${inputRef.current.dom.value},请使用组件展示。`,
}).then(() => {
setShowTable(false);
});
return;
case '表格':
if (!inputRef.current.dom.value?.trim()) {
setShowTable(true);
return;
}
}
if (viewWidgetType) {
trigger({
need: ` ${inputRef.current.dom.value},请使用适当的${viewWidgetType}组件展示。`,
}).then(() => {
setShowTable(false);
});
}
}}
/>
);
},
[isMutating, trigger, setShowTable, viewWidgetType]
);
const example = get(data, 'data.code', '').replace(/const\s+data\s?=\s?.*;?\n/, '');
functions = functions || init(t('Function for processing data'));
const Live = useMemo(() => {
let node;
try {
node = !showCode ? <LiveEditor /> : <ReactView props={props} />;
} catch (err) {
node = <LiveEditor />;
}
console.log(props, 'props');
return (
<LiveProvider
code={example}
scope={{
data: props,
import: dependenciesMap,
}}
>
{node}
<Error />
</LiveProvider>
);
}, [example, props, showCode, dependenciesMap]);
const [editor, setEditor] = useState<MonacoEditor>();
const monaco = useMonaco();
const [workerFn, { status: workerStatus, kill: workerTerminate }] = useWorker(
(data, code) => {
return new Function('data', code)(data);
},
{
autoTerminate: false,
}
);
const onKeyDown = editor?.onKeyDown!;
const KeyDownEvent = useRef<ReturnType<typeof onKeyDown>>();
useEffect(() => {
if (editor) {
if (KeyDownEvent.current) {
KeyDownEvent.current.dispose();
}
KeyDownEvent.current = editor.onKeyDown(function (event: any) {
if (event.keyCode === 2 && monaco) {
const position = editor.getPosition();
const line = editor.getModel()?.getLineContent(position!.lineNumber) || '';
const flag = /^\/\//;
if (flag.test(line)) {
// 在当前行后面提示“生成中”占位符
const position = editor.getPosition()!;
const lineNumber = position.lineNumber;
const column = position.column;
const insertText = ` ` + t('Code generation in progress, please wait...');
const op = {
range: new monaco.Range(lineNumber, column, lineNumber, column),
text: insertText,
};
editor.executeEdits('insertSnippet', [op]);
getView
.getViewFunction({
data: props,
need: line.replace(flag, ''),
})
.then((data: any) => {
const code = get(data, 'data.code');
const xml = new XML(code);
const insertText = xml.get('FunctionCode');
const nextLineNumber = lineNumber + 1;
const op1 = {
range: new monaco.Range(
lineNumber,
0,
lineNumber,
column + insertText.length
),
text: line,
};
const op2 = {
range: new monaco.Range(
lineNumber,
column + insertText.length,
nextLineNumber,
1
),
text: '\n' + insertText.trim() + '\n\n',
};
editor.executeEdits('insertSnippet', [op1, op2]);
});
}
}
});
}
}, [editor, props]);
const [functionName, setFunctionName] = useState('');
const [FunctionOption, setFunctionOption] = useState<ReturnType<typeof Parser.parse>[]>([]);
return (
<div>
{propsRaw.length ? (
<>
<div className="w-full mb-[20px]">
<Collapse>
<CollapseItem
header={
<div>
{t('Data processing functions')}
<span className="text-[var(--pc)] mx-[10px] text-[12px]">
({t('Comment + Tab to generate function')})
</span>
</div>
}
name="1"
extra={
<div className="flex">
<Select
placeholder={t('Execute function name')}
size="mini"
allowClear
style={{
width: '200px',
}}
showSearch={{
retainInputValue: true,
}}
onClick={() => {
let comments: { [line: string]: string } = {};
const root: any = Parser.parse(
editor?.getValue() || functions,
{
locations: true,
ranges: true,
sourceType: 'module',
ecmaVersion: 2020,
onComment: (...arg) => {
if (arg[1]) {
comments[arg[3] + 1] = arg[1];
}
},
}
);
const body: ReturnType<typeof Parser.parse>[] = get(
root,
'body'
);
setFunctionOption(
body.filter(v => {
set(v, 'comment', comments[v.start]);
return v.type === 'FunctionDeclaration';
})
);
}}
onChange={setFunctionName}
>
{FunctionOption.map(item => {
const name = get(item, 'id.name', '');
return (
<Select.Option key={name} value={name}>
<Tooltip content={get(item, 'comment')}>
<span className="text-[12px] leading-[24px] text-[var(--pc)] mr-[5px]">
{get(
item,
'comment',
t(
'No function description available'
)
)}
</span>
</Tooltip>
<div>{name}</div>
</Select.Option>
);
})}
</Select>
<Button
className="ml-[10px]"
onClick={async () => {
if (!functionName) {
setProps(propsRaw);
return;
}
const data = await workerFn(
propsRaw,
`${
editor?.getValue() || functions
};\n return ${functionName}(data);`
);
ConnectDb.updateQuery(
id,
`${editor?.getValue() || functions}\n`
);
setProps(data);
}}
type="primary"
size="mini"
shape="round"
>
{t('Run')}
</Button>
</div>
}
>
<Editor
onMount={(instance, monaco) => {
console.log(instance);
setEditor(instance);
}}
height="300px"
defaultLanguage="javascript"
defaultValue={functions}
/>
</CollapseItem>
</Collapse>
</div>
<div className="flex justify-between items-center mb-[20px]">
<AI
simpleMode="input"
startView={3}
inputProps={{
style: { width: '100%' },
height: 36,
className: 'overflow-hidden simple-mode-border',
prefix: selectEl,
autoFocus: false,
}}
SendButton={SendButton}
messageList={[]}
setMessageList={function (value: any[]) {
return false;
}}
/>
{data && (
<Button
shape="circle"
className="mx-[5px]"
onClick={() => setShowTable(!showTable)}
>
<IconSwap />
</Button>
)}
</div>
</>
) : (
<Alert
style={{ marginBottom: 20 }}
type="success"
content={t('Execution successful')}
/>
)}
<Spin loading={isMutating} block className="my-[20px]">
{example && !showTable ? (
<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">
{loadDependName ? (
<div className="flex justify-center">
<Spin block loading />
<div className="ml-[10px]"> {loadDependName}</div>
</div>
) : (
Live
)}
</div>
</div>
) : isEqual(props, propsRaw) && isArray(props) ? (
defaultNode || null
) : (
<ReactJson src={props} />
)}
</Spin>
</div>
);
}
export default function View() {
return null;
}
import React, { Ref } 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;
type ActionFormProp = {
form: Ref<any>;
initialValues: Record<string, any>;
};
function ActionForm({ form, initialValues }: ActionFormProp) {
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,
Descriptions,
Alert,
Collapse,
} from '@arco-design/web-react';
import { memo, useCallback, useEffect, useMemo, useReducer, useRef, 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,
forEach,
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';
import { QUERY_FUNCTION } from '@/data/prompt/functions';
import { FunctionsJson } from '@/utils/getXMLContent';
import { codeWrapper, htmlString } from '@/components/AITool/MessageItem';
import dynamic from 'next/dynamic';
// const Dag = dynamic(() => import('@/components/layout'), { ssr: false });
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 funcJson = new FunctionsJson(message);
const params = {};
forEach(funcJson.get('variablesArr'), (item: any) => {
const key = get(item, 'variable');
if (key) {
set(params, key, get(item, 'varDescription'));
}
});
return {
sql: [funcJson.get('sql').replace(/;\s+/g, ';\n')].filter(v => v.trim()),
types: ['sql'],
params,
queryName: funcJson.get('queryName'),
queryDescription: funcJson.get('queryDescription'),
};
}, [message]);
const [preViewData, setPreViewData] = useState([]);
const [rawSqlMode, switchrawSqlMode] = useState(false);
const [loading, setLoading] = useState(false);
const [modal, contextHolder] = Modal.useModal();
const [formInstance] = Form.useForm();
const { t: ct } = useTranslation('queriesList');
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) ? (
<div className="my-[10px] ">
<Typography.Paragraph className=" h-full !m-0 my-[5px] text-[rgb(var(--primary-6))] font-bold text-[12px] inline-block">
{ct('Query Description')}
</Typography.Paragraph>
{queryDescription ? (
<div className="my-[5px] text-[12px] indent-[2em]">
{queryDescription}
</div>
) : (
<Empty
description={ct('No description needed')}
icon={<span></span>}
/>
)}
{size(sql) ? (
<>
<Descriptions
border
size="mini"
title={
<div className="my-[10px] text-[rgb(var(--primary-6))] font-bold text-[15px]">
{ct('Execution Details')}
</div>
}
column={1}
data={map(sql, sql => ({
label: 'sql',
value: (
<div
className="message prose text-slate break-words overflow-hidden"
dangerouslySetInnerHTML={{
__html: htmlString(
codeWrapper('sql', sql)
),
}}
/>
),
}))}
style={{ marginBottom: 20 }}
labelStyle={{ paddingRight: 36 }}
/>
<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(),
}),
}}
>
{ct('Parameter Description')}
</Typography.Paragraph>
{Object.keys(params).length ? (
<Descriptions
border
size="mini"
column={1}
data={map(params, (value, label) => {
return { value, label };
})}
style={{ margin: '20px 0' }}
labelStyle={{ paddingRight: 36 }}
/>
) : (
<Empty
description={ct('No parameters needed')}
icon={<span></span>}
/>
)}
</>
) : null}
</div>
) : (
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;
const { data } = 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],
};
}
),
// filed: map(
// preViewData[0][0][1],
// ({ name }, i) => {
// return name as string;
// }
// ),
info: {
queryDescription,
queryName,
},
},
});
if (window.parent) {
window.parent.postMessage(
{
type: 'addApi',
data: {
...data,
},
},
'*'
);
} else {
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>
</>
) : (
<Alert
content={
<div className="text-[#7e7d7d]">
{t(
'Cannot generate executable query from the requirement description, please continue to describe the requirement!'
)}
</div>
}
/>
)}
</Form>
</div>
);
},
(p: Object, n: Object) => {
return isEqual(pick(n, ['message', 'role']), pick(p, ['message', 'role']));
}
);
component.displayName = 'MessageItem';
return component;
};
const CollapseItem = Collapse.Item;
export default function Actions() {
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 recordID = useRef<string>();
const { data, mutate } = useSWR(defaultId, () => {
// if (defaultId && recordID.current !== defaultId) {
// recordID.current = 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: { createdAt: number }, b: { createdAt: number }) =>
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: 'system',
content: `你第一步应该思考分析:用户是否指定生成模拟数据,如果有,你还需要生成模拟数据,你第二步应该思考分析:用户需求中是否需要变量(例如:生成模拟数据就不需要变量,因为数据由你生成),如果需要变量:变量的命名必须请放在$和$之间,例如:"variablesArr": [
{
"variable": "$variable$",
"varDescription": "这个变量的描述"
}):
`,
},
{
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>
{/* <Dag/> */}
<AI
isCheck
functions={QUERY_FUNCTION}
function_call={{
name: 'saveExecuteSqlInfo',
}}
stream={false}
quickTip={queryTip}
welcome={
<>
<Collapse
className="my-4 rounded-sm"
defaultActiveKey={[]}
triggerRegion="header"
>
<CollapseItem
header={
<div className="text-[16px] text-[rgb(var(--primary-6))]">
{t(
'Model Description'
)}
</div>
}
name="1"
>
{SchemaDescription ? (
SchemaDescription
) : (
<Empty
description={
<span className="text-[var(--pc)] ml-[12px]">
{t(
'In AI Analysis Model'
) +
' ...'}
</span>
}
/>
)}
</CollapseItem>
</Collapse>
<Welcome
message={t(
'Hello! I an CHAT QUERY ai, please describe your business!'
)}
role={'assistant'}
/>
</>
}
startView={4}
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 dynamic from 'next/dynamic';
const ChatView = dynamic(
() => {
return import('./View/chatView').then(({ ChatView }) => ChatView);
},
{ ssr: false }
);
const {
publicRuntimeConfig: { backendOrigin },
} = getConfig();
const FormItem = Form.Item;
export const DataTable = ({ data: preViewDataArr, loading, id, content, functions }: 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
content={content}
id={id}
props={data[0]}
functions={functions}
defaultNode={
<Table
loading={loading}
className="w-full overflow-hidden mt-[20px]"
scroll={{
x: '100%',
y: 400,
}}
stripe
border={{
wrapper: true,
cell: true,
}}
columns={map(data[1], ({ name }, i) => {
return {
title: name,
dataIndex: name,
ellipsis: true,
width: 100,
};
})}
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,
functions,
}: {
name: string;
i: number;
params: Record<string, any>;
content: {
exections: {
content: string;
type: string;
};
};
updatedAt: string;
id: string;
getListFx: () => any;
queryDescription: string;
functions: 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}
id={id}
content={content}
functions={functions}
/>
</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) : undefined;
});
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', [])}
functions={get(content, 'functions', '')}
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();
const id = params.get('id');
return (
<>
<Head>
<title>CHAT QUERY</title>
<meta
name="description"
content={ct('Data Query Based on Data Model and AI') as string}
/>
<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)',
}}
>
{id && <QueriesRender modelId={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 { some } from 'lodash';
import { reverse } from 'lodash';
import { useTranslation } from 'react-i18next';
import dagre from 'dagre';
import { each } from 'lodash';
import { findIndex } from 'lodash';
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 [addType, setAadType] = useState(1);
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,
table,
w: 230,
h: 40 + 24 + 32 * get(table, 'fields.length', 0),
});
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 autoLayout = e => {
setTableDict(state => {
var g = new dagre.graphlib.Graph();
g.setGraph({
rankdir: 'LR',
align: 'UR',
// graph: 'greedy',
// ranksep: 200,
// ranker: 'longest-path',
// nodesep: 300,
// edgesep: 100,
});
g.setDefaultEdgeLabel(function () {
return {};
});
each(state, node => {
g.setNode(node.id, {
raw: node,
width: 230,
height: 40 + 24 + 32 * get(node, 'fields.length', 0),
});
});
links.forEach(edge => {
const node = edge.endpoints[1];
const weight = findIndex(state[node.id]?.fields, v => v.id == node.fieldId);
// state[node]?.fields.length -
g.setEdge(edge.endpoints[0].id, edge.endpoints[1].id, {
weight: weight,
minlen: 2,
});
});
dagre.layout(g);
const newSate = {};
const newNodes = g.nodes().map(function (v) {
const node = g.node(v);
newSate[node.raw.id] = {
...node.raw,
x: node.x,
y: node.y,
};
});
console.log(newSate);
return newSate;
});
};
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={(kind, type = 1) => {
setShowModal(kind);
setAadType(type);
}}
setShowDrawer={setShowDrawer}
leave={saveCheck}
importDBML={importDBML}
autoLayout={autoLayout}
/>
<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}
type={addType}
/>
<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 { useTranslation } from 'react-i18next';
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 { get } from 'lodash';
import { Layout } from '@arco-design/web-react';
import Link from 'next/link';
import { useTranslation } from 'react-i18next';
import { useEffect } from 'react';
import Script from 'next/script';
declare const fireworks: any;
function Home() {
const { t } = useTranslation();
useEffect(() => {
if (get(window, 'fireworks')) {
document.querySelector('#fireworks')?.remove();
fireworks();
}
return () => {
document.querySelector('#fireworks')?.remove();
};
}, []);
return (
<>
<Head>
<title>CHAT QUERY</title>
<meta
name="description"
content={t('Data Query Based on Data Model and AI') as string}
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="home bg-scroll">
<Script
src="https://cdn.jsdelivr.net/npm/tsparticles-fireworks@2.10.1/tsparticles.fireworks.bundle.min.js"
onLoad={() => {
if (get(window, 'fireworks')) {
document.querySelector('#fireworks')?.remove();
fireworks();
}
}}
/>
<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 <Home />;
}
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
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';
import { clientsClaim, setCacheNameDetails, skipWaiting } from 'workbox-core';
// Workbox 会在这里插入预缓存列表
precacheAndRoute(self.__WB_MANIFEST);
skipWaiting(); // 跳过install 过程
clientsClaim(); // 激活所有的窗口
registerRoute(
({ url }) => {
return url.href.startsWith('https://cdn.jsdelivr.net/npm');
},
({ url, request, event }) => {
const pattern = /npm\/react@\d+\.\d+\.\d+\/\+esm/;
const pattern1 = /npm\/react-dom@\d+\.\d+\.\d+\/\+esm/;
if (pattern.test(url.href)) {
console.log(url.href, 'href');
return event.respondWith(
new Response(
`
// 使用模块导出语法导出这两个对象
const react = window.React || {};
export const Children = react.Children;
export const Component = react.Component;
export const Fragment = react.Fragment;
export const Profiler = react.Profiler;
export const PureComponent = react.PureComponent;
export const StrictMode = react.StrictMode;
export const Suspense = react.Suspense;
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = react.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
export const cloneElement = react.cloneElement;
export const createContext = react.createContext;
export const createElement = react.createElement;
export const createFactory = react.createFactory;
export const createRef = react.createRef;
export const forwardRef = react.forwardRef;
export const isValidElement = react.isValidElement;
export const lazy = react.lazy;
export const memo = react.memo;
export const startTransition = react.startTransition;
export const unstable_act = react.unstable_act;
export const useCallback = react.useCallback;
export const useContext = react.useContext;
export const useDebugValue = react.useDebugValue;
export const useDeferredValue = react.useDeferredValue;
export const useEffect = react.useEffect;
export const useId = react.useId;
export const useImperativeHandle = react.useImperativeHandle;
export const useInsertionEffect = react.useInsertionEffect;
export const useLayoutEffect = react.useLayoutEffect;
export const useMemo = react.useMemo;
export const useReducer = react.useReducer;
export const useRef = react.useRef;
export const useState = react.useState;
export const useSyncExternalStore = react.useSyncExternalStore;
export const useTransition = react.useTransition;
export const version = react.version;
export default react
`,
{
headers: { 'Content-Type': 'application/javascript' },
}
)
);
} else if (pattern1.test(url.href)) {
console.log(url.href, 'href');
return event.respondWith(
new Response(
`
// 使用模块导出语法导出这两个对象
const react = window.ReactDOM || {};
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = react.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
export const createPortal = react.createPortal;
export const createRoot = react.createRoot;
export const findDOMNode = react.findDOMNode;
export const flushSync = react.flushSync;
export const hydrate = react.hydrate;
export const hydrateRoot = react.hydrateRoot;
export const render = react.render;
export const unmountComponentAtNode = react.unmountComponentAtNode;
export const unstable_batchedUpdates = react.unstable_batchedUpdates;
export const unstable_renderSubtreeIntoContainer = react.unstable_renderSubtreeIntoContainer;
export const version = react.version;
export default react
`,
{
headers: { 'Content-Type': 'application/javascript' },
}
)
);
}
const strategy = new NetworkFirst();
return strategy.handle({ request, event });
}
);
// self.addEventListener('fetch', event => {
// if (event.request.url.startsWith('https://data.jsdelivr.com/')) {
// console.log('Fetching:', event.request.url);
// }
// });
console.log(333);
@tailwind base;
@tailwind components;
@tailwind utilities;
svg {
display: inline-block
}
.app-text {
@apply bg-gradient-to-br from-green-400 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; */
--primary-1: 255, 255, 255;
--primary-2: 255, 247, 230;
--primary-3: 255, 222, 195;
--primary-4: 255, 196, 161;
--primary-5: 255, 171, 127;
--primary-6: 255, 147, 92;
--primary-7: 255, 123, 58;
--primary-8: 255, 99, 23;
--primary-9: 235, 79, 0;
--primary-10: 176, 60, 0;
--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,0.4);
}
.selected-text{
background:rgba(255, 166, 0, 0.53) !important;
}
.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);
.arco-comment-inner{
max-width: 95%;
}
}
.shadow-t {
box-shadow: 0 -5px 10px 0px rgba(130, 130, 130, 0.1);
}
.widgetPicker{
.arco-select-view{
background: transparent !important;
border: none !important;
}
}
#fireworks{
position: relative;
z-index: -1;
}
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;
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
{
"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": ".",
"experimentalDecorators": true,
"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,
],
};
}),
};
}),
};
try {
const database = Parser.parse(combined, 'json');
return ModelExporter.export(database, databaseType, false);
} catch (e) {
console.log(e);
}
};
export default exportSQL;
import { functions, get } from 'lodash';
export class XML {
private root: HTMLDivElement;
constructor(code: string) {
this.root = document.createElement('div');
this.root.innerHTML = code;
}
get(code: string): string {
const insertText =
(this.root.querySelector('FunctionCode') as HTMLElement)?.outerText || '';
return insertText;
}
}
export class FunctionsJson {
private root: Record<string, any>;
constructor(code: string) {
try {
this.root = JSON.parse(code);
} catch {
this.root = {};
}
}
get(code: string): string {
try {
return JSON.parse(get(this.root, 'arguments', '{}'))[code] || '';
} catch (e) {
return '';
}
}
}
const gpt35 = {
name: 'gpt-3.5-turbo-16k-0613',
temperature: 0.1,
frequency_penalty: 0.1,
presence_penalty: 0,
};
const gpt4 = {
name: 'GPT-4o',
temperature: 0.1,
frequency_penalty: 0.1,
presence_penalty: 0,
};
export const models = [gpt35, gpt4];
export const getModel = (name: string) => {
for (const model of models) {
if (model.name === name) {
return model;
}
}
return {
name: name,
temperature: 0.1,
frequency_penalty: 0.1,
presence_penalty: 0,
};
};
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