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;