diff --git a/package.json b/package.json index b037e22ce..50c7be6a7 100644 --- a/package.json +++ b/package.json @@ -90,5 +90,15 @@ "yarn": "1.22.19" }, "packageManager": "yarn@1.22.1", - "version": "0.0.0" + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@mui/lab": "^5.0.0-alpha.170", + "@mui/material": "^5.15.20", + "@mui/x-charts": "^7.7.0", + "@mui/x-data-grid": "^7.7.0", + "@mui/x-date-pickers": "^7.7.0", + "mui": "^0.0.1" + } } diff --git a/packages/core/client/src/powered-by/index.tsx b/packages/core/client/src/powered-by/index.tsx index b7cbbf25a..06f7b5213 100644 --- a/packages/core/client/src/powered-by/index.tsx +++ b/packages/core/client/src/powered-by/index.tsx @@ -37,11 +37,23 @@ export const PoweredBy = () => { const appVersion = `v${data?.data?.version}`; return ( -
`, { appVersion }), - }} - >
+
+
+
Rantir Cloud
+
+ + + +
+
Made With Love From Rantir
+
+
+ Copyright ©2024 Rantir, Inc. All rights reserved. Join us in the AI, No-Code Revolution +
+
); }; diff --git a/packages/core/client/src/user/Help.tsx b/packages/core/client/src/user/Help.tsx index 48ba1a156..325a596f1 100644 --- a/packages/core/client/src/user/Help.tsx +++ b/packages/core/client/src/user/Help.tsx @@ -36,8 +36,7 @@ const SettingsMenu: React.FC<{ disabled: true, label: (
-
NocoBase
-
v{data?.data?.version}
+
Rantir
), }, @@ -48,7 +47,7 @@ const SettingsMenu: React.FC<{ { key: 'homePage', label: ( - + {t('Home page')} ), @@ -57,7 +56,7 @@ const SettingsMenu: React.FC<{ key: 'userManual', label: ( {t('Handbook')} @@ -67,7 +66,7 @@ const SettingsMenu: React.FC<{ { key: 'license', label: ( - + {t('License')} ), diff --git a/packages/plugins/@my-project/plugin-theme/src/client/CustomThemeProvider.tsx b/packages/plugins/@my-project/plugin-theme/src/client/CustomThemeProvider.tsx index f3a08abf7..ea9974980 100644 --- a/packages/plugins/@my-project/plugin-theme/src/client/CustomThemeProvider.tsx +++ b/packages/plugins/@my-project/plugin-theme/src/client/CustomThemeProvider.tsx @@ -11,6 +11,7 @@ import React from 'react'; import { ConfigProvider } from 'antd'; import { SchemaComponentOptions } from '@nocobase/client'; import './customstyles.css'; +import './modifyicon.js'; const customStyles = { backgroundColor: '#FFFF00', // Dark blue background @@ -34,45 +35,6 @@ const CustomThemeProvider: React.FC = ({ children }) => { }, }} > - {children} ); diff --git a/packages/plugins/@my-project/plugin-theme/src/client/GolosText-VariableFont_wght.ttf b/packages/plugins/@my-project/plugin-theme/src/client/GolosText-VariableFont_wght.ttf new file mode 100644 index 000000000..b68ea0266 Binary files /dev/null and b/packages/plugins/@my-project/plugin-theme/src/client/GolosText-VariableFont_wght.ttf differ diff --git a/packages/plugins/@my-project/plugin-theme/src/client/customstyles.css b/packages/plugins/@my-project/plugin-theme/src/client/customstyles.css index 33922f169..e8b0cbfe7 100644 --- a/packages/plugins/@my-project/plugin-theme/src/client/customstyles.css +++ b/packages/plugins/@my-project/plugin-theme/src/client/customstyles.css @@ -1,110 +1,12 @@ -.ant-popover-inner { - background-color: white !important; - border-radius: 4px; - } - - .ant-popover-inner .ant-popover-inner-content { - padding: 0 !important; - } - - .ant-nb-schema-initializer-menu-item { - background-color: white !important; - color: black !important; - padding: 4px 6px; - border-radius: 4px; - font-size: 11px !important; - margin: 4px 0; - } - - .ant-nb-schema-initializer-menu-item:hover { - background-color: #333 !important; - } - - /* Menu Background */ -.ant-menu { - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - } - - /* Menu Headers */ - .ant-menu-item-group-title { - font-weight: bold; - font-size: 14px; - color: #000000; - } - - /* Menu Items */ - .ant-menu-item { - font-size: 14px; - color: #000000; - padding: 8px 16px; - } - - .ant-menu-item .anticon { - color: white; - } - - .ant-menu-item:hover { - background-color: #F5F5F5; - color: #000000; - } - - /* Install New Blocks Button */ - .ant-btn { - background-color: #FFFFFF; - padding: 0px 16px; - font-size: 14px; - color: #000000; - border-radius: 0px; - } - - .ant-btn:hover { - background-color: #211E1E !important; - color: #FFF !important; - border:0px !important; - } - - .css-17black { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background-color: rgb(47, 47, 47); - padding: 0 20px; - - height: 30px; - border-radius: 7px; - color: white; - font-size: 12px; -} - -.ant-layout-header { - background-color: yellow !important; -} -.ant-menu-submenu .ant-menu-submenu-vertical { - color:black; -} - -.ant-menu-submenu-title { - color: black; -} - -span.ant-page-header-heading-title { - color: black !important; +.css-1o1fqc9 { + bottom: -30px !important; } -.anticon { - color:black; +@font-face { + font-family: GolosText; + src: url('./GolosText-VariableFont_wght.ttf'); } -.ant-menu-title-content { - color: black; +.font-family-golos { + font-family: GolosText; } \ No newline at end of file diff --git a/packages/plugins/@my-project/plugin-theme/src/client/modifyicon.js b/packages/plugins/@my-project/plugin-theme/src/client/modifyicon.js new file mode 100644 index 000000000..e1bd47d49 --- /dev/null +++ b/packages/plugins/@my-project/plugin-theme/src/client/modifyicon.js @@ -0,0 +1,169 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +function addButtonWhenParentExists() { + const intervalId = setInterval(() => { + // Select the parent div with the specific class + const parentDiv = document.querySelector('.css-14exgtg'); + + // Check if the parent div exists + if (parentDiv !== null) { + // Create the button element + const newButton = document.createElement('button'); + + const partitionButton = document.createElement('button'); + + const databaseButton = document.createElement('button'); + + // Set attributes for the button + newButton.setAttribute('data-testid', 'ai-explorer-button'); + newButton.setAttribute('title', 'AI Explore'); + newButton.setAttribute('type', 'button'); + newButton.setAttribute( + 'class', + 'ant-btn css-dev-only-do-not-override-10e56at ant-btn-default ant-btn-icon-only ant-tooltip-open', + ); + + // Set attributes for the button + partitionButton.setAttribute('data-testid', 'partition-button'); + partitionButton.setAttribute('title', 'Partition'); + partitionButton.setAttribute('type', 'button'); + partitionButton.setAttribute( + 'class', + 'ant-btn css-dev-only-do-not-override-10e56at ant-btn-default ant-btn-icon-only ant-tooltip-open', + ); + + // Set attributes for the button + databaseButton.setAttribute('data-testid', 'database-button'); + databaseButton.setAttribute('title', 'Database'); + databaseButton.setAttribute('type', 'button'); + databaseButton.setAttribute( + 'class', + 'ant-btn css-dev-only-do-not-override-10e56at ant-btn-default ant-btn-icon-only ant-tooltip-open', + ); + + // Set inner HTML for the button + newButton.innerHTML = ` + + + + + + + + + `; + + // Set inner HTML for the button + partitionButton.innerHTML = ` + + + + + + + + `; + + // Set inner HTML for the button + databaseButton.innerHTML = ` + + + + + + + + + + + + + + + `; + + // Append the button to the parent div + parentDiv.appendChild(partitionButton); + parentDiv.appendChild(databaseButton); + parentDiv.appendChild(newButton); + + // Add click event listener to the button + newButton.addEventListener('click', () => { + window.location.href = 'http://localhost:13000/explore'; + }); + + // Clear the interval to stop checking + clearInterval(intervalId); + } + }, 100); // Check every 100 milliseconds +} + +// Call the function to start checking +addButtonWhenParentExists(); + +function insertRememberMeForgotDiv() { + const intervalId = setInterval(() => { + // Select the parent div with the specific class + const parentDiv = document.querySelector('.ant-formily-layout.ant-form-vertical'); + + // Check if the parent div exists + if (parentDiv !== null && parentDiv.children.length > 1) { + // Create the new element + const rememberMeForgotDiv = document.createElement('div'); + rememberMeForgotDiv.className = 'font-family-golos remember-me-forgot'; + rememberMeForgotDiv.style.display = 'flex'; + rememberMeForgotDiv.style.justifyContent = 'space-between'; + rememberMeForgotDiv.style.marginTop = '1em'; + rememberMeForgotDiv.style.marginBottom = '1em'; + + // Create the inner content + const rememberMeDiv = document.createElement('div'); + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + rememberMeDiv.appendChild(checkbox); + rememberMeDiv.appendChild(document.createTextNode(' Remember Me')); + + const forgotPasswordDiv = document.createElement('div'); + forgotPasswordDiv.textContent = 'Forgot your password?'; + + // Append the inner content to the main div + rememberMeForgotDiv.appendChild(rememberMeDiv); + rememberMeForgotDiv.appendChild(forgotPasswordDiv); + + // Insert the new element as the second child + parentDiv.insertBefore(rememberMeForgotDiv, parentDiv.children[2]); + + // Clear the interval to stop checking + clearInterval(intervalId); + } + }, 100); // Check every 100 milliseconds +} + +// Call the function to start checking +insertRememberMeForgotDiv(); + +function addClassByAriaLabel(ariaLabel, className) { + const intervalId = setInterval(() => { + // Select the element with the specified aria-label + var element = document.querySelector('[aria-label="' + ariaLabel + '"]'); + + // Check if the element exists + if (element) { + // Add the specified class name to the element + element.classList.add(className); + + // Clear the interval to stop checking + clearInterval(intervalId); + } + }, 100); // Check every 100 milliseconds +} + +// Usage example +addClassByAriaLabel('action-Action-Sign in', 'font-family-golos'); diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignInForm.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignInForm.tsx index 3a8f75ea7..129d3b67e 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignInForm.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignInForm.tsx @@ -81,21 +81,22 @@ const passwordForm: ISchema = { htmlType: 'submit', block: true, type: 'primary', + class: 'font-family-golos', useAction: `{{ useBasicSignIn }}`, - style: { width: '100%' }, + style: { width: '30%', borderRadius: '0', backgroundColor: 'black' }, }, }, }, }, - signUp: { - type: 'void', - 'x-component': 'Link', - 'x-component-props': { - to: '{{ signUpLink }}', - }, - 'x-content': 'Create an Account', - 'x-visible': '{{ allowSignUp }}', - }, + // signUp: { + // type: 'void', + // 'x-component': 'Link', + // 'x-component-props': { + // to: '{{ signUpLink }}', + // }, + // 'x-content': 'Create an Account', + // 'x-visible': '{{ allowSignUp }}', + // }, }, }; export const SignInForm = (props: { authenticator: Authenticator }) => { diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx index 7aeaa969f..3e52dc234 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx @@ -12,7 +12,7 @@ import React, { FC } from 'react'; import { Outlet } from 'react-router-dom'; import { useSystemSettings, PoweredBy, useRequest, useAPIClient } from '@nocobase/client'; import { AuthenticatorsContext } from '../authenticator'; -import { Spin } from 'antd'; +import { Spin, Tabs } from 'antd'; export const AuthenticatorsContextProvider: FC<{ children: React.ReactNode }> = ({ children }) => { const api = useAPIClient(); @@ -44,27 +44,122 @@ export function AuthLayout() { const { data } = useSystemSettings(); return ( -
-

RANTIR TEST ACTIONS

- - - +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + Ant Design is the most influential web
+ design specification in Xihu district +
+
+
- + + + +
+
+ + Enterprise Sign In + + + + + + + + + + + + +
+
+ Sign Up +
+
+
+ +
); diff --git a/packages/plugins/@rantir/aiexplore-plugin/.npmignore b/packages/plugins/@rantir/aiexplore-plugin/.npmignore new file mode 100644 index 000000000..65f5e8779 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/src diff --git a/packages/plugins/@rantir/aiexplore-plugin/README.md b/packages/plugins/@rantir/aiexplore-plugin/README.md new file mode 100644 index 000000000..deb909ec0 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/README.md @@ -0,0 +1 @@ +# @rantir/aiexplore-plugin diff --git a/packages/plugins/@rantir/aiexplore-plugin/client.d.ts b/packages/plugins/@rantir/aiexplore-plugin/client.d.ts new file mode 100644 index 000000000..6c459cbac --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/client.d.ts @@ -0,0 +1,2 @@ +export * from './dist/client'; +export { default } from './dist/client'; diff --git a/packages/plugins/@rantir/aiexplore-plugin/client.js b/packages/plugins/@rantir/aiexplore-plugin/client.js new file mode 100644 index 000000000..b6e3be70e --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/client.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/index.js'); diff --git a/packages/plugins/@rantir/aiexplore-plugin/package.json b/packages/plugins/@rantir/aiexplore-plugin/package.json new file mode 100644 index 000000000..b16acf616 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/package.json @@ -0,0 +1,11 @@ +{ + "name": "@rantir/aiexplore-plugin", + "version": "1.0.0-alpha.13", + "main": "dist/server/index.js", + "dependencies": {}, + "peerDependencies": { + "@nocobase/client": "1.x", + "@nocobase/server": "1.x", + "@nocobase/test": "1.x" + } +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/server.d.ts b/packages/plugins/@rantir/aiexplore-plugin/server.d.ts new file mode 100644 index 000000000..c41081ddc --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/server.d.ts @@ -0,0 +1,2 @@ +export * from './dist/server'; +export { default } from './dist/server'; diff --git a/packages/plugins/@rantir/aiexplore-plugin/server.js b/packages/plugins/@rantir/aiexplore-plugin/server.js new file mode 100644 index 000000000..972842039 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/server.js @@ -0,0 +1 @@ +module.exports = require('./dist/server/index.js'); diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/client.d.ts b/packages/plugins/@rantir/aiexplore-plugin/src/client/client.d.ts new file mode 100644 index 000000000..4e96f83fa --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/client.d.ts @@ -0,0 +1,249 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +// CSS modules +type CSSModuleClasses = { readonly [key: string]: string }; + +declare module '*.module.css' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.scss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sass' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.less' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.styl' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.stylus' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.pcss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sss' { + const classes: CSSModuleClasses; + export default classes; +} + +// CSS +declare module '*.css' { } +declare module '*.scss' { } +declare module '*.sass' { } +declare module '*.less' { } +declare module '*.styl' { } +declare module '*.stylus' { } +declare module '*.pcss' { } +declare module '*.sss' { } + +// Built-in asset types +// see `src/node/constants.ts` + +// images +declare module '*.apng' { + const src: string; + export default src; +} +declare module '*.png' { + const src: string; + export default src; +} +declare module '*.jpg' { + const src: string; + export default src; +} +declare module '*.jpeg' { + const src: string; + export default src; +} +declare module '*.jfif' { + const src: string; + export default src; +} +declare module '*.pjpeg' { + const src: string; + export default src; +} +declare module '*.pjp' { + const src: string; + export default src; +} +declare module '*.gif' { + const src: string; + export default src; +} +declare module '*.svg' { + const src: string; + export default src; +} +declare module '*.ico' { + const src: string; + export default src; +} +declare module '*.webp' { + const src: string; + export default src; +} +declare module '*.avif' { + const src: string; + export default src; +} + +// media +declare module '*.mp4' { + const src: string; + export default src; +} +declare module '*.webm' { + const src: string; + export default src; +} +declare module '*.ogg' { + const src: string; + export default src; +} +declare module '*.mp3' { + const src: string; + export default src; +} +declare module '*.wav' { + const src: string; + export default src; +} +declare module '*.flac' { + const src: string; + export default src; +} +declare module '*.aac' { + const src: string; + export default src; +} +declare module '*.opus' { + const src: string; + export default src; +} +declare module '*.mov' { + const src: string; + export default src; +} +declare module '*.m4a' { + const src: string; + export default src; +} +declare module '*.vtt' { + const src: string; + export default src; +} + +// fonts +declare module '*.woff' { + const src: string; + export default src; +} +declare module '*.woff2' { + const src: string; + export default src; +} +declare module '*.eot' { + const src: string; + export default src; +} +declare module '*.ttf' { + const src: string; + export default src; +} +declare module '*.otf' { + const src: string; + export default src; +} + +// other +declare module '*.webmanifest' { + const src: string; + export default src; +} +declare module '*.pdf' { + const src: string; + export default src; +} +declare module '*.txt' { + const src: string; + export default src; +} + +// wasm?init +declare module '*.wasm?init' { + const initWasm: (options?: WebAssembly.Imports) => Promise; + export default initWasm; +} + +// web worker +declare module '*?worker' { + const workerConstructor: { + new(options?: { name?: string }): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&inline' { + const workerConstructor: { + new(options?: { name?: string }): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&url' { + const src: string; + export default src; +} + +declare module '*?sharedworker' { + const sharedWorkerConstructor: { + new(options?: { name?: string }): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?sharedworker&inline' { + const sharedWorkerConstructor: { + new(options?: { name?: string }): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?sharedworker&url' { + const src: string; + export default src; +} + +declare module '*?raw' { + const src: string; + export default src; +} + +declare module '*?url' { + const src: string; + export default src; +} + +declare module '*?inline' { + const src: string; + export default src; +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Answer.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Answer.tsx new file mode 100644 index 000000000..37360a354 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Answer.tsx @@ -0,0 +1,58 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React from 'react'; +import Bar from './Bar'; +import Line from './Line'; +import BasicTable from './Table'; +import OutlinedCard from './Card'; +import DateCalendarValue from './Date'; +import BasicPie from './Pie'; +import DataGridDemo from './DataGrid'; +import BasicScatter from './Scatter'; +import BasicTimeline from './TimeLine'; + +export const AIAnswer = ({ userQuestion, aIAnswer, nextQuestion, onQuestionClick, aiQType }) => { + const styles = { + answer: { + marginTop: '2rem', + border: '1px solid gainsboro', + borderRadius: '0.5rem', + padding: '1rem', + width: 'auto', + display: 'flex', + flexFlow: 'column', + alignItems: 'left', + }, + }; + + const items = [ + , + , + , + , + , + , + , + , + ]; + const Chart = items[Math.floor(Math.random() * items.length)]; + + return ( +
+ +
+ ); +}; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Bar.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Bar.tsx new file mode 100644 index 000000000..b5aa44947 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Bar.tsx @@ -0,0 +1,23 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { BarChart } from '@mui/x-charts/BarChart'; +import React from 'react'; + +export default function bar() { + return ( + + ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Card.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Card.tsx new file mode 100644 index 000000000..2de958ecf --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Card.tsx @@ -0,0 +1,71 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Card from '@mui/material/Card'; +import CardActions from '@mui/material/CardActions'; +import CardContent from '@mui/material/CardContent'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; + +const card = (userQuestion: string, aIAnswer: string, nextQuestion: string, onQuestionClick: any, aiQType: string) => ( + + +
{aiQType}
+
{userQuestion}
+ + {aIAnswer} +
+ + + +
+
+
+); + +export default function OutlinedCard({ userQuestion, aIAnswer, nextQuestion, onQuestionClick, aiQType }) { + return ( + + + {card(userQuestion, aIAnswer, nextQuestion, onQuestionClick, aiQType)} + + + ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/DataGrid.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/DataGrid.tsx new file mode 100644 index 000000000..89cbe6e60 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/DataGrid.tsx @@ -0,0 +1,76 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGrid, GridColDef } from '@mui/x-data-grid'; + +const columns: GridColDef<(typeof rows)[number]>[] = [ + { field: 'id', headerName: 'ID', width: 90 }, + { + field: 'firstName', + headerName: 'First name', + width: 150, + editable: true, + }, + { + field: 'lastName', + headerName: 'Last name', + width: 150, + editable: true, + }, + { + field: 'age', + headerName: 'Age', + type: 'number', + width: 110, + editable: true, + }, + { + field: 'fullName', + headerName: 'Full name', + description: 'This column has a value getter and is not sortable.', + sortable: false, + width: 160, + valueGetter: (value, row) => `${row.firstName || ''} ${row.lastName || ''}`, + }, +]; + +const rows = [ + { id: 1, lastName: 'Snow', firstName: 'Jon', age: 14 }, + { id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 31 }, + { id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 31 }, + { id: 4, lastName: 'Stark', firstName: 'Arya', age: 11 }, + { id: 5, lastName: 'Targaryen', firstName: 'Daenerys', age: null }, + { id: 6, lastName: 'Melisandre', firstName: null, age: 150 }, + { id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 }, + { id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 }, + { id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 }, +]; + +export default function DataGridDemo() { + return ( + + + + ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Date.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Date.tsx new file mode 100644 index 000000000..d8293389d --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Date.tsx @@ -0,0 +1,24 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; + +export default function DateCalendarValue() { + const [value, setValue] = React.useState(dayjs('2022-04-17')); + + return ( + + + + ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Line.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Line.tsx new file mode 100644 index 000000000..5761a5426 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Line.tsx @@ -0,0 +1,30 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { LineChart } from '@mui/x-charts/LineChart'; +import React from 'react'; + +export default function Line() { + const xLabels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']; + const pData = [10, 15, 8, 12, 7, 13, 10]; + const uData = [5, 10, 5, 8, 3, 6, 4]; + + return ( + + ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Pie.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Pie.tsx new file mode 100644 index 000000000..bf3000dc6 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Pie.tsx @@ -0,0 +1,29 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import * as React from 'react'; +import { PieChart } from '@mui/x-charts/PieChart'; + +export default function BasicPie() { + return ( + + ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Scatter.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Scatter.tsx new file mode 100644 index 000000000..7764c4463 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Scatter.tsx @@ -0,0 +1,195 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import * as React from 'react'; +import { ScatterChart } from '@mui/x-charts/ScatterChart'; + +const data = [ + { + id: 'data-0', + x1: 329.39, + x2: 391.29, + y1: 443.28, + y2: 153.9, + }, + { + id: 'data-1', + x1: 96.94, + x2: 139.6, + y1: 110.5, + y2: 217.8, + }, + { + id: 'data-2', + x1: 336.35, + x2: 282.34, + y1: 175.23, + y2: 286.32, + }, + { + id: 'data-3', + x1: 159.44, + x2: 384.85, + y1: 195.97, + y2: 325.12, + }, + { + id: 'data-4', + x1: 188.86, + x2: 182.27, + y1: 351.77, + y2: 144.58, + }, + { + id: 'data-5', + x1: 143.86, + x2: 360.22, + y1: 43.253, + y2: 146.51, + }, + { + id: 'data-6', + x1: 202.02, + x2: 209.5, + y1: 376.34, + y2: 309.69, + }, + { + id: 'data-7', + x1: 384.41, + x2: 258.93, + y1: 31.514, + y2: 236.38, + }, + { + id: 'data-8', + x1: 256.76, + x2: 70.571, + y1: 231.31, + y2: 440.72, + }, + { + id: 'data-9', + x1: 143.79, + x2: 419.02, + y1: 108.04, + y2: 20.29, + }, + { + id: 'data-10', + x1: 103.48, + x2: 15.886, + y1: 321.77, + y2: 484.17, + }, + { + id: 'data-11', + x1: 272.39, + x2: 189.03, + y1: 120.18, + y2: 54.962, + }, + { + id: 'data-12', + x1: 23.57, + x2: 456.4, + y1: 366.2, + y2: 418.5, + }, + { + id: 'data-13', + x1: 219.73, + x2: 235.96, + y1: 451.45, + y2: 181.32, + }, + { + id: 'data-14', + x1: 54.99, + x2: 434.5, + y1: 294.8, + y2: 440.9, + }, + { + id: 'data-15', + x1: 134.13, + x2: 383.8, + y1: 121.83, + y2: 273.52, + }, + { + id: 'data-16', + x1: 12.7, + x2: 270.8, + y1: 287.7, + y2: 346.7, + }, + { + id: 'data-17', + x1: 176.51, + x2: 119.17, + y1: 134.06, + y2: 74.528, + }, + { + id: 'data-18', + x1: 65.05, + x2: 78.93, + y1: 104.5, + y2: 150.9, + }, + { + id: 'data-19', + x1: 162.25, + x2: 63.707, + y1: 413.07, + y2: 26.483, + }, + { + id: 'data-20', + x1: 68.88, + x2: 150.8, + y1: 74.68, + y2: 333.2, + }, + { + id: 'data-21', + x1: 95.29, + x2: 329.1, + y1: 360.6, + y2: 422.0, + }, + { + id: 'data-22', + x1: 390.62, + x2: 10.01, + y1: 330.72, + y2: 488.06, + }, +]; + +export default function BasicScatter() { + return ( + ({ x: v.x1, y: v.y1, id: v.id })), + }, + { + label: 'Series B', + data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })), + }, + ]} + /> + ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Table.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Table.tsx new file mode 100644 index 000000000..1466081cf --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/Table.tsx @@ -0,0 +1,58 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import * as React from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; + +function createData(name: string, calories: number, fat: number, carbs: number, protein: number) { + return { name, calories, fat, carbs, protein }; +} + +const rows = [ + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Gingerbread', 356, 16.0, 49, 3.9), +]; + +export default function BasicTable() { + return ( + + + + Dessert (100g serving) + Calories + Fat (g) + Carbs (g) + Protein (g) + + + + {rows.map((row) => ( + + + {row.name} + + {row.calories} + {row.fat} + {row.carbs} + {row.protein} + + ))} + +
+ ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/TimeLine.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/TimeLine.tsx new file mode 100644 index 000000000..8e92c8096 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/components/AnswerComponents/TimeLine.tsx @@ -0,0 +1,39 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import * as React from 'react'; +import Timeline from '@mui/lab/Timeline'; +import TimelineItem from '@mui/lab/TimelineItem'; +import TimelineSeparator from '@mui/lab/TimelineSeparator'; +import TimelineConnector from '@mui/lab/TimelineConnector'; +import TimelineContent from '@mui/lab/TimelineContent'; +import TimelineDot from '@mui/lab/TimelineDot'; +import TimelineOppositeContent, { timelineOppositeContentClasses } from '@mui/lab/TimelineOppositeContent'; + +export default function BasicTimeline() { + return ( + + + 09:30 am + + + + + Eat + + + 10:00 am + + + + Code + + + ); +} diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/data/DataFunctions.ts b/packages/plugins/@rantir/aiexplore-plugin/src/client/data/DataFunctions.ts new file mode 100644 index 000000000..3274429e0 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/data/DataFunctions.ts @@ -0,0 +1,32 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export const getWebflowSites = async (token: string) => { + const endpoint = '/api/webflow_sites:list'; + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + }); + const data = await response.json(); + return data; +}; + +export const getWebflowPages = async (token: string) => { + const endpoint = '/api/webflow_pages:list'; + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + }); + const data = await response.json(); + return data; +}; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/explore.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/explore.tsx new file mode 100644 index 000000000..d8584ccb8 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/explore.tsx @@ -0,0 +1,216 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import CircularProgress from '@mui/material/CircularProgress'; +import { AIAnswer } from './components/AnswerComponents/Answer'; +import { chooseCollectionGivenQuestion, getAIAnswer } from './utils/AI'; + +const AIExplore = ({ sitesData, pagesData, token }) => { + const [inputValue, setInputValue] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [allAnswers, setAllAnswers] = useState([]); + + const containerRef = useRef(null); + + useEffect(() => { + if (containerRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + }, [allAnswers]); + + const styles = { + main: { + padding: '1rem', + display: 'flex', + flexFlow: 'column', + height: '100vh', + justifyContent: 'center', + alignItems: 'center', + }, + heading: { + color: 'black', + fontSize: '1.5rem', + marginBottom: '0.5rem', + }, + byline: { + color: 'gray', + fontSize: '0.8rem', + }, + suggestionHolder: { + display: 'grid', + gridTemplateColumns: 'repeat(2, 1fr)', + gridTemplateRows: 'repeat(2, 1fr)', + gap: '10px', + marginTop: '1rem', + marginBottom: '2rem', + }, + promptSuggestion: { + backgroundColor: 'whitesmoke', + padding: '0.8rem', + cursor: 'pointer', + }, + userInput: { + display: 'flex', + margin: '1rem', + }, + textBox: { + height: '3rem', + borderRight: '0px', + width: '30rem', + paddingLeft: '1rem', + outline: 'none', + }, + button: { + backgroundColor: 'black', + color: 'white', + height: '3rem', + width: '6rem', + }, + footer: { + marginTop: '4rem', + }, + footerMessage: { + display: 'flex', + gap: '0.5rem', + backgroundColor: '#E5EFFD', + border: '1px solid #B5D1FA', + color: '#395263', + package: '0.8rem', + }, + }; + + const suggestedPrompts = [ + 'How many sites do I have?', + 'When was my site last updated?', + 'Did I work on a page in April?', + 'How was publishing this Month?', + ]; + + const handleSubmit = async (event) => { + // change opacity for 5 seconds + event.target.style.opacity = 0.5; + setTimeout(() => { + event.target.style.opacity = 1; + }, 200); + + setIsLoading(true); + const chosenCollection = await chooseCollectionGivenQuestion(inputValue, sitesData, pagesData); + const { answer, nextQuestion } = await getAIAnswer(chosenCollection, inputValue, token, sitesData, pagesData); + + setAllAnswers([...allAnswers, { answer, nextQuestion, question: inputValue, topic: chosenCollection.type }]); + setIsLoading(false); + }; + + const detectEnterOrTab = (event) => { + // check if key pressed is enter + if (event.key === 'Enter') { + handleSubmit(event); + } + // check if key pressed is tab + if (event.key === 'Tab') { + const question = 'How many sites do I have?'; + setInputValue(question); + } + }; + + const handleInputChange = (event) => { + setInputValue(event.target.value); + }; + + const autoSubmit = async (question) => { + setInputValue(question); + setIsLoading(true); + const chosenCollection = await chooseCollectionGivenQuestion(question, sitesData, pagesData); + const { answer, nextQuestion } = await getAIAnswer(chosenCollection, question, token, sitesData, pagesData); + + setAllAnswers([...allAnswers, { answer, nextQuestion, question, topic: chosenCollection.type }]); + setIsLoading(false); + }; + + return ( +
+
+

Explore your Data

+

To start a new chat, either select a preconfigured question or enter a prompt.

+ {inputValue === '' && allAnswers.length < 1 ? ( +
+
+ {suggestedPrompts.map((item, index) => ( +
autoSubmit(item)} style={styles.promptSuggestion} key={index}> + {item} +
+ ))} +
+

+ And our finetuning model to help discover your data. Please reference all hallucinations to + support@rantir.com. +

+
+ ) : ( +
+ )} + +
+ + +
+ +
+ {allAnswers.map((item, index) => ( + autoSubmit(item.nextQuestion)} + /> + ))} +
+
+
+
+ + + + + + Ask one question at a time and start new chats for new topics. +
+
+
+ ); +}; + +AIExplore.propTypes = { + sitesData: PropTypes.object.isRequired, +}; + +export default AIExplore; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/index.tsx b/packages/plugins/@rantir/aiexplore-plugin/src/client/index.tsx new file mode 100644 index 000000000..ed63627c5 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/index.tsx @@ -0,0 +1,51 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Plugin } from '@nocobase/client'; +import React from 'react'; +import AIExplore from './explore'; +import { getWebflowSites, getWebflowPages } from './data/DataFunctions'; + +export class AiExploreClient extends Plugin { + async afterAdd() { + // await this.app.pm.add() + } + + async beforeLoad() {} + + // You can get and modify the app instance here + async load() { + // console.log(this.app); + // this.app.addComponents({}) + // this.app.addScopes({}) + // this.app.addProvider() + // this.app.addProviders() + // this.app.router.add() + try { + const token = this.app.apiClient.auth.getToken(); + const sitesData = await getWebflowSites(token); + const pagesData = await getWebflowPages(token); + + this.router.add('explore', { + path: 'explore', + Component: () => ( + + ), + }); + } catch (e) { + console.log(e); + } + } +} + +export default AiExploreClient; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/AI.ts b/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/AI.ts new file mode 100644 index 000000000..52b660bb2 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/AI.ts @@ -0,0 +1,84 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { HUGGING_FACE_KEY } from './credentials'; +import { sortStringsByFloats } from './stringUtils'; + +/** + * Use hugging face to get sentence similarity metrics + * @param data + * @returns + */ +const querySimilarity = async (data: { + inputs: { source_sentence: string; sentences: string[] }; +}): Promise => { + const response = await fetch('https://api-inference.huggingface.co/models/sentence-transformers/all-MiniLM-L6-v2', { + headers: { Authorization: 'Bearer ' + HUGGING_FACE_KEY }, + method: 'POST', + body: JSON.stringify(data), + }); + const result = await response.json(); + return result as number[]; +}; + +export const chooseCollectionGivenQuestion = async (question: string, sitesData: any, pagesData: any) => { + const choices = ['question about site info', 'question about a page']; + + const siteOrPagePredictions = await querySimilarity({ + inputs: { + source_sentence: question.toLowerCase(), + sentences: choices, + }, + }); + + const sortedPredictions: string[] = sortStringsByFloats(choices, siteOrPagePredictions); + const chosenCollection = sortedPredictions[0]; + + const info_results = { data: [], type: '' }; + if (chosenCollection === 'question about site info') { + info_results.data = sitesData.sitesData; + info_results.type = 'sites'; + } + if (chosenCollection === 'question about a page') { + info_results.data = pagesData.pagesData; + info_results.type = 'pages'; + } + return info_results; +}; + +const getRandomDataSource = (sitesData: any, pagesData: any) => { + let choices = []; + + const site = { data: sitesData['sitesData'], type: 'sites' }; + + const page = { data: pagesData['pagesData'], type: 'pages' }; + choices = [site, page]; + + return choices[Math.floor(Math.random() * choices.length)]; +}; + +export const getAIAnswer = async ( + info_results: any, + question: string, + token: string, + sitesData: any, + pagesData: any, +) => { + const resp = await fetch('/api/ai:ask', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ question, info: info_results, nextQDataSource: getRandomDataSource(sitesData, pagesData) }), + }); + + const { data } = await resp.json(); + return data; +}; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/credentials.ts b/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/credentials.ts new file mode 100644 index 000000000..594cf570a --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/credentials.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export const HUGGING_FACE_KEY = `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`; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/stringUtils.ts b/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/stringUtils.ts new file mode 100644 index 000000000..f4ae9070a --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/client/utils/stringUtils.ts @@ -0,0 +1,21 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +function sortStringsByFloats(strings: string[], floats: number[]) { + // Create an object that maps each string to its corresponding float value + const stringToFloatMap = strings.reduce((map: { [key: string]: number }, str, index) => { + map[str] = floats[index]; + return map; + }, {}); + + // Sort + return strings.sort((a, b) => stringToFloatMap[b] - stringToFloatMap[a]); +} + +export { sortStringsByFloats }; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/index.ts b/packages/plugins/@rantir/aiexplore-plugin/src/index.ts new file mode 100644 index 000000000..be99a2ff1 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/index.ts @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './server'; +export { default } from './server'; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/server/AI.ts b/packages/plugins/@rantir/aiexplore-plugin/src/server/AI.ts new file mode 100644 index 000000000..bf9114c70 --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/server/AI.ts @@ -0,0 +1,130 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +const ANTHROPIC_API_KEY = + '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'; + +function getReadableDateTime(utcString: string): string { + const date = new Date(utcString); + const month = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + const monthName = month[date.getMonth()]; + const day = date.getDate(); + const year = date.getFullYear(); + const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + const dayOfWeek = daysOfWeek[date.getDay()]; + const dateString = `${dayOfWeek} ${day} ${monthName}, ${year}`; + return `${dateString}`; +} + +/** + * Get natural language response given structured results. + * @param user_question + * @param info_results + * @returns + */ + +export const getNaturalLangugageAnswer = async (user_question: string, info_results: { [key: string]: string }[]) => { + const currentDate = new Date(); + const dateString = "Today's date is " + getReadableDateTime(currentDate.toString()); + let info = `${dateString}.\n${info_results.length} pages/sites found.\n`; + + info_results.forEach((resultObj) => { + for (const key in resultObj) { + if (key !== 'timeFilterPassed' && key !== 'id') info += key + ' is ' + resultObj[key] + '. '; + } + info += ' \n'; + }); + const prompt = `Use the supporting info below to answer the user question below. Be terse. \n\nUser Question:\n${user_question}\n\nSupporting Info:\n${info}`; + // console.log(prompt); + + const requestBody = { + model: 'claude-3-haiku-20240307', //'claude-3-sonnet-20240229', + max_tokens: 1024, + temperature: 0.0, + messages: [{ role: 'user', content: prompt }], + }; + + const requestOptions = { + method: 'POST', + headers: { + 'x-api-key': ANTHROPIC_API_KEY, + 'anthropic-version': '2023-06-01', + 'content-type': 'application/json', + }, + body: JSON.stringify(requestBody), + }; + + const res = await fetch('https://api.anthropic.com/v1/messages', requestOptions); + const { content }: { content: { text: string }[] } = await res.json(); + if (content) return content[0].text; + return ''; +}; + +/** + * Get natural language response given structured results. + * @param user_question + * @param info_results + * @returns + */ + +export const getNaturalLangugageQuestion = async ( + user_question: string, + info_results: { data: any[]; type: string }, +) => { + let info = `\n`; + + const forbiddenKeys = ['timeFilterPassed', 'id', 'createdAt', 'updatedAt']; + + info_results.data.forEach((resultObj, index) => { + for (const key in resultObj) { + if (!forbiddenKeys.includes(key)) info += key + ' is ' + resultObj[key] + '. '; + } + info += ' \n\n'; + }); + + const topics = Object.keys(info_results.data[0]); + const topic = topics[Math.floor(topics.length * Math.random())]; + const prompt = `Write a question about the following ${topic} that can be answered by the following ${info_results.type} info:\n${info} \n Be terse. Do not refer to info by number`; + //console.log(prompt); + + const requestBody = { + model: 'claude-3-haiku-20240307', //'claude-3-sonnet-20240229', + max_tokens: 1024, + temperature: 1, + messages: [{ role: 'user', content: prompt }], + }; + + const requestOptions = { + method: 'POST', + headers: { + 'x-api-key': ANTHROPIC_API_KEY, + 'anthropic-version': '2023-06-01', + 'content-type': 'application/json', + }, + body: JSON.stringify(requestBody), + }; + + const res = await fetch('https://api.anthropic.com/v1/messages', requestOptions); + const { content }: { content: { text: string }[] } = await res.json(); + if (content) return content[0].text; + return ''; +}; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/server/collections/.gitkeep b/packages/plugins/@rantir/aiexplore-plugin/src/server/collections/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/server/index.ts b/packages/plugins/@rantir/aiexplore-plugin/src/server/index.ts new file mode 100644 index 000000000..be989de7c --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/server/index.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export { default } from './plugin'; diff --git a/packages/plugins/@rantir/aiexplore-plugin/src/server/plugin.ts b/packages/plugins/@rantir/aiexplore-plugin/src/server/plugin.ts new file mode 100644 index 000000000..418f77f1d --- /dev/null +++ b/packages/plugins/@rantir/aiexplore-plugin/src/server/plugin.ts @@ -0,0 +1,45 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Plugin } from '@nocobase/server'; +import { getNaturalLangugageAnswer, getNaturalLangugageQuestion } from './AI'; + +export class AiExploreServer extends Plugin { + async afterAdd() {} + + async beforeLoad() {} + + async load() { + this.app.resourceManager.define({ + name: 'ai', + actions: { + async ask(ctx, next) { + console.log('ask', ctx.request.body); + const question = ctx.request.body.question; + const userDBdata = ctx.request.body.info; + const qDataSource = ctx.request.body.nextQDataSource; + const answer = await getNaturalLangugageAnswer(question, userDBdata.data); + const nextQuestion = await getNaturalLangugageQuestion(question, qDataSource); + ctx.body = { answer, nextQuestion }; + next(); + }, + }, + }); + } + + async install() {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default AiExploreServer;