mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-11 13:58:58 +00:00
feat: Front-end engineering replacement
This commit is contained in:
@@ -1 +0,0 @@
|
||||
API_BASE_URL=http://127.0.0.1:5000
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"printWidth": 150,
|
||||
"semi": true,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"arrowParens": "always"
|
||||
}
|
@@ -36,6 +36,7 @@ Also, it is a **LLM to Vision** solution.
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) >= 16
|
||||
- [npm](https://npmjs.com/) >= 8
|
||||
- [yarn](https://yarnpkg.com/) >= 1.22
|
||||
- Supported OSes: Linux, macOS and Windows
|
||||
|
||||
@@ -43,6 +44,7 @@ Also, it is a **LLM to Vision** solution.
|
||||
|
||||
```sh
|
||||
# Install dependencies
|
||||
npm install
|
||||
yarn install
|
||||
```
|
||||
|
||||
@@ -54,12 +56,14 @@ edit the `API_BASE_URL` to the real address
|
||||
|
||||
```sh
|
||||
# development model
|
||||
npm run dev
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## 🚀 Use In DB-GPT
|
||||
|
||||
```sh
|
||||
npm run compile
|
||||
yarn compile
|
||||
|
||||
# copy compile file to DB-GPT static file dictory
|
||||
@@ -90,4 +94,40 @@ Enjoy using DB-GPT-Web to build stunning UIs for your AI and GPT projects.
|
||||
|
||||
For any queries or issues, feel free to open an [issue](https://github.com/eosphoros-ai/DB-GPT-Web/issues) on the repository.
|
||||
|
||||
Happy coding! 😊
|
||||
Happy coding! 😊
|
||||
|
||||
|
||||
## antdbgptweb installation
|
||||
|
||||
### deploy in local environment:
|
||||
|
||||
1. 在 /etc/hosts文件增加一行, 必须配置本地xxx.alipay.net,否则无法接入线下的登陆系统
|
||||
```
|
||||
127.0.0.1 local.alipay.net
|
||||
```
|
||||
|
||||
2. 在.env文件增加下面配置
|
||||
deploy in local environment:
|
||||
```
|
||||
ANT_BUC_GET_USER_URL='http://antbuservice.stable.alipay.net/pub/getLoginUser.json?appName=antdbgpt'
|
||||
ANT_BUC_NOT_LOGIN_URL='http://antbuservice.stable.alipay.net/pub/userNotLogin.htm?appName=antdbgpt&sourceUrl='
|
||||
API_BASE_URL='http://local.alipay.net:5000'
|
||||
```
|
||||
|
||||
OR modify file next.config.js
|
||||
```
|
||||
env: {
|
||||
API_BASE_URL: process.env.API_BASE_URL || "http://local.alipay.net:5000",
|
||||
GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
|
||||
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
||||
ANT_BUC_GET_USER_URL: 'http://antbuservice.stable.alipay.net/pub/getLoginUser.json?appName=antdbgpt',
|
||||
ANT_BUC_NOT_LOGIN_URL: 'http://antbuservice.stable.alipay.net/pub/userNotLogin.htm?appName=antdbgpt&sourceUrl='
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
deploy in production environment:
|
||||
```
|
||||
ANT_BUC_GET_USER_URL=https://antbuservice.alipay.com/pub/getLoginUser.json?appName=antdbgpt
|
||||
ANT_BUC_NOT_LOGIN_URL=https://antbuservice.alipay.com/pub/userNotLogin.htm?appName=antdbgpt&sourceUrl=
|
||||
```
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { apiInterceptors, getDialogueList, getUsableModels } from '@/client/api';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { apiInterceptors, getDialogueList, getUsableModels, queryAdminList } from '@/client/api';
|
||||
import { ChatHistoryResponse, DialogueListResponse, IChatDialogueSchema } from '@/types/chat';
|
||||
import { UserInfoResponse } from '@/types/userinfo';
|
||||
import { getUserId } from '@/utils';
|
||||
import { STORAGE_THEME_KEY } from '@/utils/constants/index';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { STORAGE_THEME_KEY } from '@/utils';
|
||||
import { createContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
type ThemeMode = 'dark' | 'light';
|
||||
|
||||
@@ -14,8 +16,8 @@ interface IChatContext {
|
||||
scene: IChatDialogueSchema['chat_mode'] | (string & {});
|
||||
chatId: string;
|
||||
model: string;
|
||||
modelList: string[];
|
||||
dbParam?: string;
|
||||
modelList: Array<string>;
|
||||
agent: string;
|
||||
dialogueList?: DialogueListResponse;
|
||||
setAgent?: (val: string) => void;
|
||||
@@ -24,13 +26,19 @@ interface IChatContext {
|
||||
setIsContract: (val: boolean) => void;
|
||||
setIsMenuExpand: (val: boolean) => void;
|
||||
setDbParam: (val: string) => void;
|
||||
queryDialogueList: () => void;
|
||||
refreshDialogList: () => void;
|
||||
currentDialogue?: DialogueListResponse[0];
|
||||
history: ChatHistoryResponse;
|
||||
setHistory: (val: ChatHistoryResponse) => void;
|
||||
docId?: number;
|
||||
setDocId: (docId: number) => void;
|
||||
// 当前对话信息
|
||||
currentDialogInfo: {
|
||||
chat_scene: string;
|
||||
app_code: string;
|
||||
};
|
||||
setCurrentDialogInfo: (val: { chat_scene: string; app_code: string }) => void;
|
||||
adminList: UserInfoResponse[];
|
||||
refreshDialogList?: any;
|
||||
}
|
||||
|
||||
function getDefaultTheme(): ThemeMode {
|
||||
@@ -43,8 +51,8 @@ const ChatContext = createContext<IChatContext>({
|
||||
mode: 'light',
|
||||
scene: '',
|
||||
chatId: '',
|
||||
modelList: [],
|
||||
model: '',
|
||||
modelList: [],
|
||||
dbParam: undefined,
|
||||
dialogueList: [],
|
||||
agent: '',
|
||||
@@ -53,13 +61,18 @@ const ChatContext = createContext<IChatContext>({
|
||||
setIsContract: () => {},
|
||||
setIsMenuExpand: () => {},
|
||||
setDbParam: () => void 0,
|
||||
queryDialogueList: () => {},
|
||||
refreshDialogList: () => {},
|
||||
setMode: () => void 0,
|
||||
history: [],
|
||||
setHistory: () => {},
|
||||
docId: undefined,
|
||||
setDocId: () => {},
|
||||
currentDialogInfo: {
|
||||
chat_scene: '',
|
||||
app_code: '',
|
||||
},
|
||||
setCurrentDialogInfo: () => {},
|
||||
adminList: [],
|
||||
refreshDialogList: () => {},
|
||||
});
|
||||
|
||||
const ChatContextProvider = ({ children }: { children: React.ReactElement }) => {
|
||||
@@ -67,7 +80,6 @@ const ChatContextProvider = ({ children }: { children: React.ReactElement }) =>
|
||||
const chatId = searchParams?.get('id') ?? '';
|
||||
const scene = searchParams?.get('scene') ?? '';
|
||||
const db_param = searchParams?.get('db_param') ?? '';
|
||||
|
||||
const [isContract, setIsContract] = useState(false);
|
||||
const [model, setModel] = useState<string>('');
|
||||
const [isMenuExpand, setIsMenuExpand] = useState<boolean>(scene !== 'chat_dashboard');
|
||||
@@ -76,51 +88,66 @@ const ChatContextProvider = ({ children }: { children: React.ReactElement }) =>
|
||||
const [history, setHistory] = useState<ChatHistoryResponse>([]);
|
||||
const [docId, setDocId] = useState<number>();
|
||||
const [mode, setMode] = useState<ThemeMode>('light');
|
||||
// 管理员列表
|
||||
const [adminList, setAdminList] = useState<UserInfoResponse[]>([]);
|
||||
|
||||
const {
|
||||
run: queryDialogueList,
|
||||
data: dialogueList = [],
|
||||
refresh: refreshDialogList,
|
||||
} = useRequest(
|
||||
async () => {
|
||||
const [, res] = await apiInterceptors(getDialogueList());
|
||||
return res ?? [];
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogueList.length && scene === 'chat_agent') {
|
||||
const agent = dialogueList.find((item) => item.conv_uid === chatId)?.select_param;
|
||||
agent && setAgent(agent);
|
||||
}
|
||||
}, [dialogueList, scene, chatId]);
|
||||
const [currentDialogInfo, setCurrentDialogInfo] = useState({
|
||||
chat_scene: '',
|
||||
app_code: '',
|
||||
});
|
||||
|
||||
// 获取model
|
||||
const { data: modelList = [] } = useRequest(async () => {
|
||||
const [, res] = await apiInterceptors(getUsableModels());
|
||||
return res ?? [];
|
||||
});
|
||||
|
||||
// 获取管理员列表
|
||||
const { run: queryAdminListRun } = useRequest(
|
||||
async () => {
|
||||
const [, res] = await apiInterceptors(queryAdminList({ role: 'admin' }));
|
||||
return res ?? [];
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setAdminList(data);
|
||||
},
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (getUserId()) {
|
||||
queryAdminListRun();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [queryAdminListRun, getUserId()]);
|
||||
|
||||
useEffect(() => {
|
||||
setMode(getDefaultTheme());
|
||||
try {
|
||||
const dialogInfo = JSON.parse(localStorage.getItem('cur_dialog_info') || '');
|
||||
setCurrentDialogInfo(dialogInfo);
|
||||
} catch (error) {
|
||||
setCurrentDialogInfo({
|
||||
chat_scene: '',
|
||||
app_code: '',
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setModel(modelList[0]);
|
||||
}, [modelList, modelList?.length]);
|
||||
|
||||
const currentDialogue = useMemo(() => dialogueList.find((item: any) => item.conv_uid === chatId), [chatId, dialogueList]);
|
||||
const contextValue = {
|
||||
isContract,
|
||||
isMenuExpand,
|
||||
scene,
|
||||
chatId,
|
||||
modelList,
|
||||
model,
|
||||
modelList,
|
||||
dbParam: dbParam || db_param,
|
||||
dialogueList,
|
||||
agent,
|
||||
setAgent,
|
||||
mode,
|
||||
@@ -129,13 +156,13 @@ const ChatContextProvider = ({ children }: { children: React.ReactElement }) =>
|
||||
setIsContract,
|
||||
setIsMenuExpand,
|
||||
setDbParam,
|
||||
queryDialogueList,
|
||||
refreshDialogList,
|
||||
currentDialogue,
|
||||
history,
|
||||
setHistory,
|
||||
docId,
|
||||
setDocId,
|
||||
currentDialogInfo,
|
||||
setCurrentDialogInfo,
|
||||
adminList,
|
||||
};
|
||||
return <ChatContext.Provider value={contextValue}>{children}</ChatContext.Provider>;
|
||||
};
|
||||
|
457
web/app/i18n.ts
457
web/app/i18n.ts
@@ -1,464 +1,13 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
const en = {
|
||||
Knowledge_Space: 'Knowledge',
|
||||
space: 'space',
|
||||
Vector: 'Vector',
|
||||
Owner: 'Owner',
|
||||
Count: 'Count',
|
||||
File_type_Invalid: 'The file type is invalid',
|
||||
Knowledge_Space_Config: 'Space Config',
|
||||
Choose_a_Datasource_type: 'Datasource type',
|
||||
Segmentation: 'Segmentation',
|
||||
No_parameter: `No segementation parameter required.`,
|
||||
Knowledge_Space_Name: 'Knowledge Space Name',
|
||||
Please_input_the_name: 'Please input the name',
|
||||
Please_input_the_owner: 'Please input the owner',
|
||||
Please_select_file: 'Please select one file',
|
||||
Description: 'Description',
|
||||
Storage: 'Storage',
|
||||
Please_input_the_description: 'Please input the description',
|
||||
Please_select_the_storage: 'Please select the storage',
|
||||
Please_select_the_domain_type: 'Please select the domain type',
|
||||
Next: 'Next',
|
||||
the_name_can_only_contain: 'the name can only contain numbers, letters, Chinese characters, "-" and "_"',
|
||||
Text: 'Text',
|
||||
'Fill your raw text': 'Fill your raw text',
|
||||
URL: 'URL',
|
||||
Fetch_the_content_of_a_URL: 'Fetch the content of a URL',
|
||||
Document: 'Document',
|
||||
Upload_a_document: 'Upload a document, document type can be PDF, CSV, Text, PowerPoint, Word, Markdown',
|
||||
Name: 'Name',
|
||||
Text_Source: 'Text Source(Optional)',
|
||||
Please_input_the_text_source: 'Please input the text source',
|
||||
Sync: 'Sync',
|
||||
Back: 'Back',
|
||||
Finish: 'Finish',
|
||||
Web_Page_URL: 'Web Page URL',
|
||||
Please_input_the_Web_Page_URL: 'Please input the Web Page URL',
|
||||
Select_or_Drop_file: 'Select or Drop file',
|
||||
Documents: 'Documents',
|
||||
Chat: 'Chat',
|
||||
Add_Datasource: 'Add Datasource',
|
||||
View_Graph: 'View Graph',
|
||||
Arguments: 'Arguments',
|
||||
Type: 'Type',
|
||||
Size: 'Size',
|
||||
Last_Sync: 'Last Sync',
|
||||
Status: 'Status',
|
||||
Result: 'Result',
|
||||
Details: 'Details',
|
||||
Delete: 'Delete',
|
||||
Operation: 'Operation',
|
||||
Submit: 'Submit',
|
||||
Chunks: 'Chunks',
|
||||
Content: 'Content',
|
||||
Meta_Data: 'Meta Data',
|
||||
Please_select_a_file: 'Please select a file',
|
||||
Please_input_the_text: 'Please input the text',
|
||||
Embedding: 'Embedding',
|
||||
topk: 'topk',
|
||||
the_top_k_vectors: 'the top k vectors based on similarity score',
|
||||
recall_score: 'recall_score',
|
||||
Set_a_threshold_score: 'Set a threshold score for the retrieval of similar vectors',
|
||||
recall_type: 'recall_type',
|
||||
model: 'model',
|
||||
A_model_used: 'A model used to create vector representations of text or other data',
|
||||
Automatic: 'Automatic',
|
||||
Process: 'Process',
|
||||
Automatic_desc: 'Automatically set segmentation and preprocessing rules.',
|
||||
chunk_size: 'chunk_size',
|
||||
The_size_of_the_data_chunks: 'The size of the data chunks used in processing',
|
||||
chunk_overlap: 'chunk_overlap',
|
||||
The_amount_of_overlap: 'The amount of overlap between adjacent data chunks',
|
||||
Prompt: 'Prompt',
|
||||
scene: 'scene',
|
||||
A_contextual_parameter: 'A contextual parameter used to define the setting or environment in which the prompt is being used',
|
||||
template: 'template',
|
||||
structure_or_format:
|
||||
'A pre-defined structure or format for the prompt, which can help ensure that the AI system generates responses that are consistent with the desired style or tone.',
|
||||
max_token: 'max_token',
|
||||
max_iteration: 'max_iteration',
|
||||
concurrency_limit: 'concurrency_limit',
|
||||
The_maximum_number_of_tokens: 'The maximum number of tokens or words allowed in a prompt',
|
||||
Theme: 'Theme',
|
||||
Port: 'Port',
|
||||
Username: 'Username',
|
||||
Password: 'Password',
|
||||
Remark: 'Remark',
|
||||
Edit: 'Edit',
|
||||
Database: 'Database',
|
||||
Data_Source: 'Data Center',
|
||||
Close_Sidebar: 'Fold',
|
||||
Show_Sidebar: 'UnFold',
|
||||
language: 'Language',
|
||||
choose_model: 'Please choose a model',
|
||||
data_center_desc: 'DB-GPT also offers a user-friendly data center management interface for efficient data maintenance.',
|
||||
create_database: 'Create Database',
|
||||
create_knowledge: 'Create Knowledge',
|
||||
path: 'Path',
|
||||
model_manage: 'Models',
|
||||
stop_model_success: 'Stop model success',
|
||||
create_model: 'Create Model',
|
||||
model_select_tips: 'Please select a model',
|
||||
language_select_tips: 'Please select a language',
|
||||
submit: 'Submit',
|
||||
close: 'Close',
|
||||
start_model_success: 'Start model success',
|
||||
download_model_tip: 'Please download model first.',
|
||||
Plugins: 'Plugins',
|
||||
try_again: 'Try again',
|
||||
no_data: 'No data',
|
||||
Open_Sidebar: 'Unfold',
|
||||
cancel: 'Cancel',
|
||||
Edit_Success: 'Edit Success',
|
||||
Add: 'Add',
|
||||
Add_Success: 'Add Success',
|
||||
Error_Message: 'Something Error',
|
||||
Please_Input: 'Please Input',
|
||||
Prompt_Info_Scene: 'Scene',
|
||||
Prompt_Info_Sub_Scene: 'Sub Scene',
|
||||
Prompt_Info_Name: 'Name',
|
||||
Prompt_Info_Content: 'Content',
|
||||
Public: 'Public',
|
||||
Private: 'Private',
|
||||
Lowest: 'Lowest',
|
||||
Missed: 'Missed',
|
||||
Lost: 'Lost',
|
||||
Incorrect: 'Incorrect',
|
||||
Verbose: 'Verbose',
|
||||
Best: 'Best',
|
||||
Rating: 'Rating',
|
||||
Q_A_Category: 'Q&A Category',
|
||||
Q_A_Rating: 'Q&A Rating',
|
||||
feed_back_desc:
|
||||
'0: No results\n' +
|
||||
'1: Results exist, but they are irrelevant, the question is not understood\n' +
|
||||
'2: Results exist, the question is understood, but it indicates that the question cannot be answered\n' +
|
||||
'3: Results exist, the question is understood, and an answer is given, but the answer is incorrect\n' +
|
||||
'4: Results exist, the question is understood, the answer is correct, but it is verbose and lacks a summary\n' +
|
||||
'5: Results exist, the question is understood, the answer is correct, the reasoning is correct, and a summary is provided, concise and to the point\n',
|
||||
input_count: 'Total input',
|
||||
input_unit: 'characters',
|
||||
Copy: 'Copy',
|
||||
Copy_success: 'Content copied successfully',
|
||||
Copy_nothing: 'Content copied is empty',
|
||||
Copry_error: 'Copy failed',
|
||||
Click_Select: 'Click&Select',
|
||||
Quick_Start: 'Quick Start',
|
||||
Select_Plugins: 'Select Plugins',
|
||||
Search: 'Search',
|
||||
Update_From_Github: 'Upload From Github',
|
||||
Reset: 'Reset',
|
||||
Upload: 'Upload',
|
||||
Market_Plugins: 'Market Plugin',
|
||||
My_Plugins: 'My Plugins',
|
||||
Del_Knowledge_Tips: 'Do you want delete the Space',
|
||||
Del_Document_Tips: 'Do you want delete the Document',
|
||||
Tips: 'Tips',
|
||||
Limit_Upload_File_Count_Tips: 'Only one file can be uploaded at a time',
|
||||
To_Plugin_Market: 'Go to the Plugin Market',
|
||||
Summary: 'Summary',
|
||||
stacked_column_chart: 'Stacked Column',
|
||||
column_chart: 'Column',
|
||||
percent_stacked_column_chart: 'Percent Stacked Column',
|
||||
grouped_column_chart: 'Grouped Column',
|
||||
time_column: 'Time Column',
|
||||
pie_chart: 'Pie',
|
||||
line_chart: 'Line',
|
||||
area_chart: 'Area',
|
||||
stacked_area_chart: 'Stacked Area',
|
||||
scatter_plot: 'Scatter',
|
||||
bubble_chart: 'Bubble',
|
||||
stacked_bar_chart: 'Stacked Bar',
|
||||
bar_chart: 'Bar',
|
||||
percent_stacked_bar_chart: 'Percent Stacked Bar',
|
||||
grouped_bar_chart: 'Grouped Bar',
|
||||
water_fall_chart: 'Waterfall',
|
||||
table: 'Table',
|
||||
multi_line_chart: 'Multi Line',
|
||||
multi_measure_column_chart: 'Multi Measure Column',
|
||||
multi_measure_line_chart: 'Multi Measure Line',
|
||||
Advices: 'Advices',
|
||||
Retry: 'Retry',
|
||||
Load_more: 'load more',
|
||||
new_chat: 'New Chat',
|
||||
choice_agent_tip: 'Please choose an agent',
|
||||
no_context_tip: 'Please enter your question',
|
||||
Terminal: 'Terminal',
|
||||
awel_flow: 'AWEL Flow',
|
||||
save: 'Save',
|
||||
add_node: 'Add Node',
|
||||
no_node: 'No Node',
|
||||
connect_warning: 'Nodes cannot be connected',
|
||||
flow_modal_title: 'Save Flow',
|
||||
flow_name: 'Flow Name',
|
||||
flow_description: 'Flow Description',
|
||||
flow_name_required: 'Please enter the flow name',
|
||||
flow_description_required: 'Please enter the flow description',
|
||||
save_flow_success: 'Save flow success',
|
||||
delete_flow_confirm: 'Are you sure you want to delete this flow?',
|
||||
related_nodes: 'Related Nodes',
|
||||
add_resource: 'Add Resource',
|
||||
team_modal: 'Work Modal',
|
||||
App: 'App',
|
||||
resource_name: 'Resource Name',
|
||||
resource_type: 'Resource Type',
|
||||
resource_value: 'Value',
|
||||
resource_dynamic: 'Dynamic',
|
||||
Please_input_the_work_modal: 'Please select the work modal',
|
||||
available_resources: ' Available Resources',
|
||||
edit_new_applications: 'Edit new applications',
|
||||
collect: 'Collect',
|
||||
collected: 'Collected',
|
||||
create: 'Create',
|
||||
Agents: 'Agents',
|
||||
edit_application: 'edit application',
|
||||
add_application: 'add application',
|
||||
app_name: 'App Name',
|
||||
LLM_strategy: 'LLM Strategy',
|
||||
LLM_strategy_value: 'LLM Strategy Value',
|
||||
resource: 'Resource',
|
||||
operators: 'Operators',
|
||||
Chinese: 'Chinese',
|
||||
English: 'English',
|
||||
refreshSuccess: 'Refresh Success',
|
||||
Download: 'Download'
|
||||
} as const;
|
||||
import en from '@/locales/en';
|
||||
import zh from '@/locales/zh';
|
||||
|
||||
export type I18nKeys = keyof typeof en;
|
||||
|
||||
export interface Resources {
|
||||
interface Resources {
|
||||
translation: Record<I18nKeys, string>;
|
||||
}
|
||||
|
||||
const zh: Resources['translation'] = {
|
||||
Knowledge_Space: '知识库',
|
||||
space: '知识库',
|
||||
Vector: '向量',
|
||||
Owner: '创建人',
|
||||
Count: '文档数',
|
||||
File_type_Invalid: '文件类型错误',
|
||||
Knowledge_Space_Config: '知识库配置',
|
||||
Choose_a_Datasource_type: '知识库类型',
|
||||
Segmentation: '分片',
|
||||
No_parameter: '不需要配置分片参数',
|
||||
Knowledge_Space_Name: '知识库名称',
|
||||
Please_input_the_name: '请输入名称',
|
||||
Please_input_the_owner: '请输入创建人',
|
||||
Please_select_file: '请至少选择一个文件',
|
||||
Description: '描述',
|
||||
Storage: '存储类型',
|
||||
Domain: '领域类型',
|
||||
Please_input_the_description: '请输入描述',
|
||||
Please_select_the_storage: '请选择存储类型',
|
||||
Please_select_the_domain_type: '请选择领域类型',
|
||||
Next: '下一步',
|
||||
the_name_can_only_contain: '名称只能包含数字、字母、中文字符、-或_',
|
||||
Text: '文本',
|
||||
'Fill your raw text': '填写您的原始文本',
|
||||
URL: '网址',
|
||||
Fetch_the_content_of_a_URL: '获取 URL 的内容',
|
||||
Document: '文档',
|
||||
Upload_a_document: '上传文档,文档类型可以是PDF、CSV、Text、PowerPoint、Word、Markdown',
|
||||
Name: '名称',
|
||||
Text_Source: '文本来源(可选)',
|
||||
Please_input_the_text_source: '请输入文本来源',
|
||||
Sync: '同步',
|
||||
Back: '上一步',
|
||||
Finish: '完成',
|
||||
Web_Page_URL: '网页网址',
|
||||
Please_input_the_Web_Page_URL: '请输入网页网址',
|
||||
Select_or_Drop_file: '选择或拖拽文件',
|
||||
Documents: '文档',
|
||||
Chat: '对话',
|
||||
Add_Datasource: '添加数据源',
|
||||
View_Graph: '查看图谱',
|
||||
Arguments: '参数',
|
||||
Type: '类型',
|
||||
Size: '切片',
|
||||
Last_Sync: '上次同步时间',
|
||||
Status: '状态',
|
||||
Result: '结果',
|
||||
Details: '明细',
|
||||
Delete: '删除',
|
||||
Operation: '操作',
|
||||
Submit: '提交',
|
||||
close: '关闭',
|
||||
Chunks: '切片',
|
||||
Content: '内容',
|
||||
Meta_Data: '元数据',
|
||||
Please_select_a_file: '请上传一个文件',
|
||||
Please_input_the_text: '请输入文本',
|
||||
Embedding: '嵌入',
|
||||
topk: 'TopK',
|
||||
the_top_k_vectors: '基于相似度得分的前 k 个向量',
|
||||
recall_score: '召回分数',
|
||||
Set_a_threshold_score: '设置相似向量检索的阈值分数',
|
||||
recall_type: '召回类型',
|
||||
model: '模型',
|
||||
A_model_used: '用于创建文本或其他数据的矢量表示的模型',
|
||||
Automatic: '自动切片',
|
||||
Process: '切片处理',
|
||||
Automatic_desc: '自动设置分割和预处理规则。',
|
||||
chunk_size: '块大小',
|
||||
The_size_of_the_data_chunks: '处理中使用的数据块的大小',
|
||||
chunk_overlap: '块重叠',
|
||||
The_amount_of_overlap: '相邻数据块之间的重叠量',
|
||||
scene: '场景',
|
||||
A_contextual_parameter: '用于定义使用提示的设置或环境的上下文参数',
|
||||
template: '模板',
|
||||
structure_or_format: '预定义的提示结构或格式,有助于确保人工智能系统生成与所需风格或语气一致的响应。',
|
||||
max_token: '最大令牌',
|
||||
max_iteration: '最大迭代',
|
||||
concurrency_limit: '并发限制',
|
||||
The_maximum_number_of_tokens: '提示中允许的最大标记或单词数',
|
||||
Theme: '主题',
|
||||
Port: '端口',
|
||||
Username: '用户名',
|
||||
Password: '密码',
|
||||
Remark: '备注',
|
||||
Edit: '编辑',
|
||||
Database: '数据库',
|
||||
Data_Source: '数据中心',
|
||||
Close_Sidebar: '收起',
|
||||
Show_Sidebar: '展开',
|
||||
language: '语言',
|
||||
choose_model: '请选择一个模型',
|
||||
data_center_desc: 'DB-GPT支持数据库交互和基于文档的对话,它还提供了一个用户友好的数据中心管理界面。',
|
||||
create_database: '创建数据库',
|
||||
create_knowledge: '创建知识库',
|
||||
path: '路径',
|
||||
model_manage: '模型管理',
|
||||
stop_model_success: '模型停止成功',
|
||||
create_model: '创建模型',
|
||||
model_select_tips: '请选择一个模型',
|
||||
submit: '提交',
|
||||
start_model_success: '启动模型成功',
|
||||
download_model_tip: '请先下载模型!',
|
||||
Plugins: '插件列表',
|
||||
try_again: '刷新重试',
|
||||
no_data: '暂无数据',
|
||||
Prompt: '提示语',
|
||||
Open_Sidebar: '展开',
|
||||
cancel: '取消',
|
||||
Edit_Success: '编辑成功',
|
||||
Add: '新增',
|
||||
Add_Success: '新增成功',
|
||||
Error_Message: '出错了',
|
||||
Please_Input: '请输入',
|
||||
Prompt_Info_Scene: '场景',
|
||||
Prompt_Info_Sub_Scene: '次级场景',
|
||||
Prompt_Info_Name: '名称',
|
||||
Prompt_Info_Content: '内容',
|
||||
Public: '公共',
|
||||
Private: '私有',
|
||||
Lowest: '渣渣',
|
||||
Missed: '没理解',
|
||||
Lost: '答不了',
|
||||
Incorrect: '答错了',
|
||||
Verbose: '较啰嗦',
|
||||
Best: '真棒',
|
||||
Rating: '评分',
|
||||
Q_A_Category: '问答类别',
|
||||
Q_A_Rating: '问答评分',
|
||||
feed_back_desc:
|
||||
'0: 无结果\n' +
|
||||
'1: 有结果,但是在文不对题,没有理解问题\n' +
|
||||
'2: 有结果,理解了问题,但是提示回答不了这个问题\n' +
|
||||
'3: 有结果,理解了问题,并做出回答,但是回答的结果错误\n' +
|
||||
'4: 有结果,理解了问题,回答结果正确,但是比较啰嗦,缺乏总结\n' +
|
||||
'5: 有结果,理解了问题,回答结果正确,推理正确,并给出了总结,言简意赅\n',
|
||||
input_count: '共计输入',
|
||||
input_unit: '字',
|
||||
Copy: '复制',
|
||||
Copy_success: '内容复制成功',
|
||||
Copy_nothing: '内容复制为空',
|
||||
Copry_error: '复制失败',
|
||||
Click_Select: '点击选择',
|
||||
Quick_Start: '快速开始',
|
||||
Select_Plugins: '选择插件',
|
||||
Search: '搜索',
|
||||
Reset: '重置',
|
||||
Update_From_Github: '更新Github插件',
|
||||
Upload: '上传',
|
||||
Market_Plugins: '插件市场',
|
||||
My_Plugins: '我的插件',
|
||||
Del_Knowledge_Tips: '你确定删除该知识库吗',
|
||||
Del_Document_Tips: '你确定删除该文档吗',
|
||||
Tips: '提示',
|
||||
Limit_Upload_File_Count_Tips: '一次只能上传一个文件',
|
||||
To_Plugin_Market: '前往插件市场',
|
||||
Summary: '总结',
|
||||
stacked_column_chart: '堆叠柱状图',
|
||||
column_chart: '柱状图',
|
||||
percent_stacked_column_chart: '百分比堆叠柱状图',
|
||||
grouped_column_chart: '簇形柱状图',
|
||||
time_column: '簇形柱状图',
|
||||
pie_chart: '饼图',
|
||||
line_chart: '折线图',
|
||||
area_chart: '面积图',
|
||||
stacked_area_chart: '堆叠面积图',
|
||||
scatter_plot: '散点图',
|
||||
bubble_chart: '气泡图',
|
||||
stacked_bar_chart: '堆叠条形图',
|
||||
bar_chart: '条形图',
|
||||
percent_stacked_bar_chart: '百分比堆叠条形图',
|
||||
grouped_bar_chart: '簇形条形图',
|
||||
water_fall_chart: '瀑布图',
|
||||
table: '表格',
|
||||
multi_line_chart: '多折线图',
|
||||
multi_measure_column_chart: '多指标柱形图',
|
||||
multi_measure_line_chart: '多指标折线图',
|
||||
Advices: '自动推荐',
|
||||
Retry: '重试',
|
||||
Load_more: '加载更多',
|
||||
new_chat: '创建会话',
|
||||
choice_agent_tip: '请选择代理',
|
||||
no_context_tip: '请输入你的问题',
|
||||
Terminal: '终端',
|
||||
awel_flow: 'AWEL 工作流',
|
||||
save: '保存',
|
||||
add_node: '添加节点',
|
||||
no_node: '没有可编排节点',
|
||||
connect_warning: '节点无法连接',
|
||||
flow_modal_title: '保存工作流',
|
||||
flow_name: '工作流名称',
|
||||
flow_description: '工作流描述',
|
||||
flow_name_required: '请输入工作流名称',
|
||||
flow_description_required: '请输入工作流描述',
|
||||
save_flow_success: '保存工作流成功',
|
||||
delete_flow_confirm: '确定删除该工作流吗?',
|
||||
related_nodes: '关联节点',
|
||||
language_select_tips: '请选择语言',
|
||||
add_resource: '添加资源',
|
||||
team_modal: '工作模式',
|
||||
App: '应用程序',
|
||||
resource: '资源',
|
||||
resource_name: '资源名',
|
||||
resource_type: '资源类型',
|
||||
resource_value: '参数',
|
||||
resource_dynamic: '动态',
|
||||
Please_input_the_work_modal: '请选择工作模式',
|
||||
available_resources: '可用资源',
|
||||
edit_new_applications: '编辑新的应用',
|
||||
collect: '收藏',
|
||||
collected: '已收藏',
|
||||
create: '创建',
|
||||
Agents: '智能体',
|
||||
edit_application: '编辑应用',
|
||||
add_application: '添加应用',
|
||||
app_name: '应用名称',
|
||||
LLM_strategy: '模型策略',
|
||||
LLM_strategy_value: '模型策略参数',
|
||||
operators: '算子',
|
||||
Chinese: '中文',
|
||||
English: '英文',
|
||||
refreshSuccess: '刷新成功',
|
||||
Download: '下载',
|
||||
} as const;
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
en: {
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { getUserId } from '@/utils';
|
||||
import { HEADER_USER_ID_KEY } from '@/utils/constants/index';
|
||||
import axios, { AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios';
|
||||
|
||||
export type ResponseType<T = any> = {
|
||||
@@ -39,8 +41,9 @@ const LONG_TIME_API: string[] = [
|
||||
ins.interceptors.request.use((request) => {
|
||||
const isLongTimeApi = LONG_TIME_API.some((item) => request.url && request.url.indexOf(item) >= 0);
|
||||
if (!request.timeout) {
|
||||
request.timeout = isLongTimeApi ? 60000 : 10000;
|
||||
request.timeout = isLongTimeApi ? 60000 : 100000;
|
||||
}
|
||||
request.headers.set(HEADER_USER_ID_KEY, getUserId());
|
||||
return request;
|
||||
});
|
||||
|
||||
@@ -66,3 +69,10 @@ export const DELETE = <Params = any, Response = any, D = any>(url: string, param
|
||||
|
||||
export * from './tools';
|
||||
export * from './request';
|
||||
export * from './chat';
|
||||
export * from './flow';
|
||||
export * from './app';
|
||||
export * from './knowledge';
|
||||
export * from './user';
|
||||
export * from './prompt';
|
||||
export * from './evaluate';
|
||||
|
@@ -1,8 +1,23 @@
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { DELETE, GET, POST, PUT } from '.';
|
||||
import { DbListResponse, DbSupportTypeResponse, PostDbParams, ChatFeedBackSchema, PostDbRefreshParams } from '@/types/db';
|
||||
import { DialogueListResponse, IChatDialogueSchema, NewDialogueParam, SceneResponse, ChatHistoryResponse, FeedBack, IDB } from '@/types/chat';
|
||||
import { IModelData, StartModelParams, BaseModelParams, SupportModel } from '@/types/model';
|
||||
import {
|
||||
GetDBGPTsListResponse,
|
||||
PostAgentHubUpdateParams,
|
||||
PostAgentMyPluginResponse,
|
||||
PostAgentPluginResponse,
|
||||
PostAgentQueryParams,
|
||||
} from '@/types/agent';
|
||||
import { GetAppInfoParams, IApp, IAgent, IAppData } from '@/types/app';
|
||||
import {
|
||||
ChatHistoryResponse,
|
||||
DialogueListResponse,
|
||||
FeedBack,
|
||||
IChatDialogueSchema,
|
||||
IDB,
|
||||
NewDialogueParam,
|
||||
SceneResponse,
|
||||
UserParam,
|
||||
UserParamResponse,
|
||||
} from '@/types/chat';
|
||||
import { ChatFeedBackSchema, DbListResponse, DbSupportTypeResponse, PostDbParams, PostDbRefreshParams } from '@/types/db';
|
||||
import {
|
||||
GetEditorSQLRoundRequest,
|
||||
GetEditorySqlParams,
|
||||
@@ -11,13 +26,7 @@ import {
|
||||
PostEditorSQLRunParams,
|
||||
PostSQLEditorSubmitParams,
|
||||
} from '@/types/editor';
|
||||
import {
|
||||
PostAgentHubUpdateParams,
|
||||
PostAgentQueryParams,
|
||||
PostAgentPluginResponse,
|
||||
PostAgentMyPluginResponse,
|
||||
GetDBGPTsListResponse,
|
||||
} from '@/types/agent';
|
||||
import { IFlow, IFlowNode, IFlowUpdateParam, IFlowResponse } from '@/types/flow';
|
||||
import {
|
||||
AddKnowledgeParams,
|
||||
ArgumentsParams,
|
||||
@@ -33,16 +42,21 @@ import {
|
||||
ISyncBatchResponse,
|
||||
SpaceConfig,
|
||||
} from '@/types/knowledge';
|
||||
import { UpdatePromptParams, IPrompt, PromptParams } from '@/types/prompt';
|
||||
import { IFlow, IFlowNode, IFlowResponse, IFlowUpdateParam } from '@/types/flow';
|
||||
import { IAgent, IApp, IAppData, ITeamModal } from '@/types/app';
|
||||
import { BaseModelParams, IModelData, StartModelParams, SupportModel } from '@/types/model';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { DELETE, GET, POST, PUT } from '.';
|
||||
|
||||
|
||||
/** App */
|
||||
export const postScenes = () => {
|
||||
return POST<null, Array<SceneResponse>>('/api/v1/chat/dialogue/scenes');
|
||||
};
|
||||
export const newDialogue = (data: NewDialogueParam) => {
|
||||
return POST<NewDialogueParam, IChatDialogueSchema>('/api/v1/chat/dialogue/new', data);
|
||||
return POST<NewDialogueParam, IChatDialogueSchema>(`/api/v1/chat/dialogue/new?chat_mode=${data.chat_mode}&model_name=${data.model}`, data);
|
||||
};
|
||||
|
||||
export const addUser = (data: UserParam) => {
|
||||
return POST<UserParam, UserParamResponse>('/api/v1/user/add', data);
|
||||
};
|
||||
|
||||
/** Database Page */
|
||||
@@ -90,15 +104,19 @@ export const postChatModeParamsFileLoad = ({
|
||||
data,
|
||||
config,
|
||||
model,
|
||||
userName,
|
||||
sysCode,
|
||||
}: {
|
||||
convUid: string;
|
||||
chatMode: string;
|
||||
data: FormData;
|
||||
model: string;
|
||||
userName?: string;
|
||||
sysCode?: string;
|
||||
config?: Omit<AxiosRequestConfig, 'headers'>;
|
||||
}) => {
|
||||
return POST<FormData, ChatHistoryResponse>(
|
||||
`/api/v1/chat/mode/params/file/load?conv_uid=${convUid}&chat_mode=${chatMode}&model_name=${model}`,
|
||||
return POST<FormData, any>(
|
||||
`/api/v1/resource/file/upload?conv_uid=${convUid}&chat_mode=${chatMode}&model_name=${model}&user_name=${userName}&sys_code=${sysCode}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
@@ -109,6 +127,10 @@ export const postChatModeParamsFileLoad = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const clearChatHistory = (conUid: string) => {
|
||||
return POST<null, Record<string, string>>(`/api/v1/chat/dialogue/clear?con_uid=${conUid}`);
|
||||
};
|
||||
|
||||
/** Menu */
|
||||
export const delDialogue = (conv_uid: string) => {
|
||||
return POST(`/api/v1/chat/dialogue/delete?con_uid=${conv_uid}`);
|
||||
@@ -139,8 +161,8 @@ export const saveArguments = (knowledgeName: string, data: ArgumentsParams) => {
|
||||
return POST<ArgumentsParams, IArguments>(`/knowledge/${knowledgeName}/argument/save`, data);
|
||||
};
|
||||
|
||||
export const getSpaceList = () => {
|
||||
return POST<any, Array<ISpace>>('/knowledge/space/list', {});
|
||||
export const getSpaceList = (data: any) => {
|
||||
return POST<any, Array<ISpace>>('/knowledge/space/list', data);
|
||||
};
|
||||
export const getDocumentList = (spaceName: string, data: Record<string, number | Array<number>>) => {
|
||||
return POST<Record<string, number | Array<number>>, IDocumentResponse>(`/knowledge/${spaceName}/document/list`, data);
|
||||
@@ -154,7 +176,7 @@ export const addDocument = (knowledgeName: string, data: DocumentParams) => {
|
||||
};
|
||||
|
||||
export const addSpace = (data: AddKnowledgeParams) => {
|
||||
return POST<AddKnowledgeParams, Array<any>>(`/knowledge/space/add`, data);
|
||||
return POST<AddKnowledgeParams, number>(`/knowledge/space/add`, data);
|
||||
};
|
||||
|
||||
export const getChunkStrategies = () => {
|
||||
@@ -178,7 +200,7 @@ export const getChunkList = (spaceName: string, data: ChunkListParams) => {
|
||||
};
|
||||
|
||||
export const delDocument = (spaceName: string, data: Record<string, number>) => {
|
||||
return POST<Record<string, number>>(`/knowledge/${spaceName}/document/delete`, data);
|
||||
return POST<Record<string, number>, null>(`/knowledge/${spaceName}/document/delete`, data);
|
||||
};
|
||||
|
||||
export const delSpace = (data: Record<string, string>) => {
|
||||
@@ -248,27 +270,12 @@ export const postChatFeedBackForm = ({ data, config }: { data: ChatFeedBackSchem
|
||||
};
|
||||
|
||||
/** prompt */
|
||||
export const getPromptList = (data: PromptParams) => {
|
||||
return POST<PromptParams, Array<IPrompt>>('/prompt/list', data);
|
||||
};
|
||||
|
||||
export const updatePrompt = (data: UpdatePromptParams) => {
|
||||
return POST<UpdatePromptParams, []>('/prompt/update', data);
|
||||
};
|
||||
|
||||
export const addPrompt = (data: UpdatePromptParams) => {
|
||||
return POST<UpdatePromptParams, []>('/prompt/add', data);
|
||||
};
|
||||
|
||||
/** AWEL Flow */
|
||||
export const addFlow = (data: IFlowUpdateParam) => {
|
||||
return POST<IFlowUpdateParam, IFlow>('/api/v1/serve/awel/flows', data);
|
||||
};
|
||||
|
||||
export const getFlows = () => {
|
||||
return GET<null, IFlowResponse>('/api/v1/serve/awel/flows');
|
||||
};
|
||||
|
||||
export const getFlowById = (id: string) => {
|
||||
return GET<null, IFlow>(`/api/v1/serve/awel/flows/${id}`);
|
||||
};
|
||||
@@ -286,13 +293,6 @@ export const getFlowNodes = () => {
|
||||
};
|
||||
|
||||
/** app */
|
||||
export const addApp = (data: IApp) => {
|
||||
return POST<IApp, []>('/api/v1/app/create', data);
|
||||
};
|
||||
|
||||
export const getAppList = (data: Record<string, string>) => {
|
||||
return POST<Record<string, string>, IAppData>('/api/v1/app/list', data);
|
||||
};
|
||||
|
||||
export const collectApp = (data: Record<string, string>) => {
|
||||
return POST<Record<string, string>, []>('/api/v1/app/collect', data);
|
||||
@@ -302,36 +302,51 @@ export const unCollectApp = (data: Record<string, string>) => {
|
||||
return POST<Record<string, string>, []>('/api/v1/app/uncollect', data);
|
||||
};
|
||||
|
||||
export const delApp = (data: Record<string, string>) => {
|
||||
return POST<Record<string, string>, []>('/api/v1/app/remove', data);
|
||||
};
|
||||
|
||||
export const getAgents = () => {
|
||||
return GET<object, IAgent[]>('/api/v1/agents/list', {});
|
||||
};
|
||||
|
||||
export const getTeamMode = () => {
|
||||
return GET<null, string[]>('/api/v1/team-mode/list');
|
||||
};
|
||||
|
||||
export const getResourceType = () => {
|
||||
return GET<null, string[]>('/api/v1/resource-type/list');
|
||||
};
|
||||
|
||||
export const getResource = (data: Record<string, string>) => {
|
||||
return GET<Record<string, string>, []>(`/api/v1/app/resources/list?type=${data.type}`);
|
||||
export const publishApp = (app_code: string) => {
|
||||
return POST<Record<string, any>, []>('/api/v1/app/publish', { app_code });
|
||||
};
|
||||
|
||||
export const updateApp = (data: IApp) => {
|
||||
return POST<IApp, []>('/api/v1/app/edit', data);
|
||||
export const unPublishApp = (app_code: string) => {
|
||||
return POST<Record<string, any>, []>('/api/v1/app/unpublish', { app_code });
|
||||
};
|
||||
export const addOmcDB = (params: Record<string, string>) => {
|
||||
return POST<Record<string, any>, []>('/api/v1/chat/db/add', params);
|
||||
};
|
||||
|
||||
export const getAppStrategy = () => {
|
||||
return GET<null, []>(`/api/v1/llm-strategy/list`);
|
||||
export const getAppInfo = (data: GetAppInfoParams) => {
|
||||
return GET<GetAppInfoParams, IApp>('/api/v1/app/info', data);
|
||||
};
|
||||
|
||||
export const getAppStrategyValues = (type: string) => {
|
||||
return GET<string, []>(`/api/v1/llm-strategy/value/list?type=${type}`);
|
||||
export const getSupportDBList = (db_name = '') => {
|
||||
return GET<null, Record<string, any>>(`/api/v1/permission/db/list?db_name=${db_name}`);
|
||||
};
|
||||
|
||||
export const recommendApps = (data: Record<string, string>) => {
|
||||
return POST<Record<string, string>, []>('/api/v1/app/hot/list', data);
|
||||
};
|
||||
export const flowSearch = (data: Record<string, string>) => {
|
||||
return POST<Record<string, string>, []>('/api/v1/serve/awel/flows', data);
|
||||
};
|
||||
export const modelSearch = (data: Record<string, string>) => {
|
||||
return POST<Record<string, string>, []>('/api/controller/models', data);
|
||||
};
|
||||
|
||||
export const getKnowledgeAdmins = (spaceId: string) => {
|
||||
return GET<string, Record<string, any>>(`/knowledge/users/list?space_id=${spaceId}`);
|
||||
};
|
||||
export const updateKnowledgeAdmins = (data: Record<string, string>) => {
|
||||
return POST<Record<string, any>, any[]>(`/knowledge/users/update`, data);
|
||||
};
|
||||
|
||||
/** AWEL Flow */
|
||||
|
||||
/** app */
|
||||
export const delApp = (data: Record<string, string>) => {
|
||||
return POST<Record<string, string>, []>('/api/v1/app/remove', data);
|
||||
};
|
||||
|
||||
export const getSpaceConfig = () => {
|
||||
|
@@ -6,6 +6,8 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
import MyEmpty from '../common/MyEmpty';
|
||||
import { ClearOutlined, DownloadOutlined, GithubOutlined, LoadingOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import BlurredCard, { ChatButton, InnerDropdown } from '@/ant-components/common/blurredCard';
|
||||
import moment from 'moment';
|
||||
|
||||
function MarketPlugins() {
|
||||
const { t } = useTranslation();
|
||||
@@ -96,6 +98,7 @@ function MarketPlugins() {
|
||||
},
|
||||
[actionIndex, pluginAction],
|
||||
);
|
||||
console.log(agents);
|
||||
|
||||
return (
|
||||
<Spin spinning={loading}>
|
||||
@@ -114,8 +117,7 @@ function MarketPlugins() {
|
||||
</Form>
|
||||
{!agents.length && !loading && <MyEmpty error={isError} refresh={refresh} />}
|
||||
<div className="flex flex-wrap gap-2 md:gap-4">
|
||||
{agents.map((agent, index) => (
|
||||
<Card
|
||||
{/* <Card
|
||||
className="w-full md:w-1/2 lg:w-1/3 xl:w-1/4"
|
||||
key={agent.id}
|
||||
actions={[
|
||||
@@ -142,7 +144,50 @@ function MarketPlugins() {
|
||||
<Tooltip title={agent.description}>
|
||||
<p className="mt-2 line-clamp-2 text-gray-400 text-sm">{agent.description}</p>
|
||||
</Tooltip>
|
||||
</Card>
|
||||
</Card> */}
|
||||
{agents.map((agent, index) => (
|
||||
<BlurredCard
|
||||
onClick={() => {
|
||||
window.open(agent.storage_url, '_blank');
|
||||
}}
|
||||
description={agent.description}
|
||||
name={agent.name}
|
||||
key={agent.id}
|
||||
Tags={
|
||||
<div>
|
||||
{agent.author && <Tag>{agent.author}</Tag>}
|
||||
{agent.version && <Tag>v{agent.version}</Tag>}
|
||||
{agent.type && <Tag>Type {agent.type}</Tag>}
|
||||
{agent.storage_channel && <Tag>{agent.storage_channel}</Tag>}
|
||||
</div>
|
||||
}
|
||||
LeftBottom={
|
||||
<div className="flex gap-2">
|
||||
<span>{agent.author}</span>
|
||||
<span>•</span>
|
||||
{agent?.gmt_created && <span>{moment(agent?.gmt_created).fromNow() + ' ' + t('update')}</span>}
|
||||
</div>
|
||||
}
|
||||
RightBottom={
|
||||
agent.installed ? (
|
||||
<ChatButton
|
||||
Icon={<ClearOutlined />}
|
||||
text="Uninstall"
|
||||
onClick={() => {
|
||||
pluginAction(agent.name, index, false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ChatButton
|
||||
Icon={<DownloadOutlined />}
|
||||
text="Install"
|
||||
onClick={() => {
|
||||
pluginAction(agent.name, index, true);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Spin>
|
||||
|
@@ -40,7 +40,7 @@ export default function AgentPanel(props: IProps) {
|
||||
const getStrategy = async () => {
|
||||
const [_, data] = await apiInterceptors(getAppStrategy());
|
||||
if (data) {
|
||||
setStrategyOptions(data?.map((item) => ({ label: item, value: item })));
|
||||
setStrategyOptions(data?.map((item) => ({ label: item.name_cn, value: item.value })));
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,28 +1,30 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
import { apiInterceptors, collectApp, delApp, newDialogue, unCollectApp } from '@/client/api';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { apiInterceptors, collectApp, delApp, newDialogue, publishApp, unCollectApp, unPublishApp } from '@/client/api';
|
||||
import { IApp } from '@/types/app';
|
||||
import { DeleteFilled, MessageFilled, StarFilled, WarningOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Modal, Popconfirm, Tooltip, message } from 'antd';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import IconFont from '@/ant-components/common/Icon';
|
||||
import { useRequest } from 'ahooks';
|
||||
import GPTCard from '../common/gpt-card';
|
||||
|
||||
interface IProps {
|
||||
updateApps: (data?: { is_collected: boolean }) => void;
|
||||
updateApps: (params?: Record<string, any>) => void;
|
||||
app: IApp;
|
||||
handleEdit: (app: any) => void;
|
||||
isCollected: boolean;
|
||||
activeKey: string;
|
||||
}
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
export default function AppCard(props: IProps) {
|
||||
const { updateApps, app, handleEdit, isCollected } = props;
|
||||
const { updateApps, app, handleEdit, activeKey } = props;
|
||||
const { model } = useContext(ChatContext);
|
||||
const router = useRouter();
|
||||
|
||||
const [isCollect, setIsCollect] = useState<string>(app.is_collected);
|
||||
const { setAgent: setAgentToChat } = useContext(ChatContext);
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -42,30 +44,135 @@ export default function AppCard(props: IProps) {
|
||||
cancelText: 'No',
|
||||
async onOk() {
|
||||
await apiInterceptors(delApp({ app_code: app.app_code }));
|
||||
updateApps(isCollected ? { is_collected: isCollected } : undefined);
|
||||
if (activeKey === 'collected') {
|
||||
updateApps({ is_collected: 'true', ignore_user: 'true' });
|
||||
} else {
|
||||
updateApps();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsCollect(app.is_collected);
|
||||
}, [app]);
|
||||
|
||||
const collect = async () => {
|
||||
const [error] = await apiInterceptors(isCollect === 'true' ? unCollectApp({ app_code: app.app_code }) : collectApp({ app_code: app.app_code }));
|
||||
const [error] = await apiInterceptors(
|
||||
app.is_collected === 'true' ? unCollectApp({ app_code: app.app_code }) : collectApp({ app_code: app.app_code }),
|
||||
);
|
||||
if (error) return;
|
||||
updateApps(isCollected ? { is_collected: isCollected } : undefined);
|
||||
setIsCollect(isCollect === 'true' ? 'false' : 'true');
|
||||
if (activeKey === 'collected') {
|
||||
updateApps({ is_collected: 'true', ignore_user: 'true' });
|
||||
} else if (activeKey === 'common') {
|
||||
updateApps({ ignore_user: 'true', published: 'true' });
|
||||
} else {
|
||||
updateApps();
|
||||
}
|
||||
};
|
||||
|
||||
const handleChat = async () => {
|
||||
setAgentToChat?.(app.app_code);
|
||||
const [, res] = await apiInterceptors(newDialogue({ chat_mode: 'chat_agent' }));
|
||||
if (res) {
|
||||
router.push(`/chat/?scene=chat_agent&id=${res.conv_uid}${model ? `&model=${model}` : ''}`);
|
||||
// 原生应用跳转
|
||||
if (app.team_mode === 'native_app') {
|
||||
const { chat_scene = '' } = app.team_context;
|
||||
router.push(`/chat?scene=${chat_scene}&id=${res.conv_uid}${model ? `&model=${model}` : ''}`);
|
||||
} else {
|
||||
setAgentToChat?.(app.app_code);
|
||||
router.push(`/chat/?scene=chat_agent&id=${res.conv_uid}${model ? `&model=${model}` : ''}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 发布或取消发布应用
|
||||
const { run: operate } = useRequest(
|
||||
async () => {
|
||||
if (app.published === 'true') {
|
||||
return await apiInterceptors(unPublishApp(app.app_code));
|
||||
} else {
|
||||
return await apiInterceptors(publishApp(app.app_code));
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (data) => {
|
||||
if (data[2]?.success) {
|
||||
if (app.published === 'true') {
|
||||
message.success(t('cancel_success'));
|
||||
} else {
|
||||
message.success(t('published_success'));
|
||||
}
|
||||
}
|
||||
updateApps?.();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const publicContent = () => {
|
||||
const { published = '' } = app;
|
||||
const stopPropagationFn = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
return (
|
||||
<Popconfirm
|
||||
title={t('Tips')}
|
||||
description={t(published == 'true' ? 'unPublish_desc' : 'publish_desc')}
|
||||
onCancel={(e: any) => {
|
||||
stopPropagationFn(e);
|
||||
}}
|
||||
onConfirm={async (e: any) => {
|
||||
stopPropagationFn(e);
|
||||
operate();
|
||||
}}
|
||||
>
|
||||
<Tooltip title={t(published == 'true' ? 'unPublish' : 'publish')}>
|
||||
{published == 'true' ? (
|
||||
<IconFont
|
||||
type="icon-unPublish-cloud"
|
||||
style={{
|
||||
fontSize: 20,
|
||||
}}
|
||||
onClick={stopPropagationFn}
|
||||
/>
|
||||
) : (
|
||||
<IconFont
|
||||
type="icon-publish-cloud"
|
||||
style={{
|
||||
fontSize: 20,
|
||||
}}
|
||||
onClick={stopPropagationFn}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
);
|
||||
};
|
||||
|
||||
const canDelete = useMemo(() => {
|
||||
return activeKey === 'app';
|
||||
}, [activeKey]);
|
||||
|
||||
const operations = useMemo(() => {
|
||||
const defaultArr = [
|
||||
{
|
||||
label: t('Chat'),
|
||||
children: <MessageFilled />,
|
||||
onClick: handleChat,
|
||||
},
|
||||
{
|
||||
label: t('collect'),
|
||||
children: <StarFilled className={app.is_collected === 'false' ? 'text-gray-400' : 'text-yellow-400'} />,
|
||||
onClick: collect,
|
||||
},
|
||||
];
|
||||
if (canDelete) {
|
||||
defaultArr.push({
|
||||
label: t('Delete'),
|
||||
children: <DeleteFilled />,
|
||||
onClick: () => showDeleteConfirm() as any,
|
||||
});
|
||||
}
|
||||
return defaultArr;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [app, canDelete]);
|
||||
|
||||
return (
|
||||
<GPTCard
|
||||
title={app.app_name}
|
||||
@@ -77,27 +184,13 @@ export default function AppCard(props: IProps) {
|
||||
{ text: app.team_mode, color: 'default' },
|
||||
]}
|
||||
onClick={() => {
|
||||
if (!canDelete) {
|
||||
return;
|
||||
}
|
||||
handleEdit(app);
|
||||
}}
|
||||
operations={[
|
||||
{
|
||||
label: t('Chat'),
|
||||
children: <MessageFilled />,
|
||||
onClick: handleChat,
|
||||
},
|
||||
{
|
||||
label: t('collect'),
|
||||
children: <StarFilled className={app.is_collected === 'false' ? 'text-gray-400' : 'text-yellow-400'} />,
|
||||
onClick: collect,
|
||||
},
|
||||
{
|
||||
label: t('Delete'),
|
||||
children: <DeleteFilled />,
|
||||
onClick: () => {
|
||||
showDeleteConfirm();
|
||||
},
|
||||
},
|
||||
]}
|
||||
operations={operations}
|
||||
extraContent={canDelete && publicContent()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { AgentParams, IAgent as IAgentParams, IApp, IDetail } from '@/types/app';
|
||||
import { AgentParams, IAgent as IAgentParams, CreateAppParams, IDetail } from '@/types/app';
|
||||
import { Dropdown, Form, Input, Modal, Select, Space, Spin, Tabs } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -60,7 +60,7 @@ export default function AppModal(props: IProps) {
|
||||
setActiveKey(newActiveKey);
|
||||
};
|
||||
|
||||
const createApp = async (app: IApp) => {
|
||||
const createApp = async (app: CreateAppParams) => {
|
||||
await apiInterceptors(type === 'add' ? addApp(app) : updateApp(app));
|
||||
await updateApps();
|
||||
};
|
||||
@@ -302,7 +302,7 @@ export default function AppModal(props: IProps) {
|
||||
<div>
|
||||
<Modal
|
||||
okText={t('Submit')}
|
||||
title={type === 'edit' ? t('edit_application') : t('add_application')}
|
||||
title={type === 'edit' ? 'edit application' : 'add application'}
|
||||
open={open}
|
||||
width={'65%'}
|
||||
onCancel={handleCancel}
|
||||
@@ -327,7 +327,7 @@ export default function AppModal(props: IProps) {
|
||||
autoComplete="off"
|
||||
onFinish={handleSubmit}
|
||||
>
|
||||
<Form.Item<FieldType> label={t('app_name')} name="app_name" rules={[{ required: true, message: t('Please_input_the_name') }]}>
|
||||
<Form.Item<FieldType> label={'App Name'} name="app_name" rules={[{ required: true, message: t('Please_input_the_name') }]}>
|
||||
<Input placeholder={t('Please_input_the_name')} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
@@ -353,7 +353,7 @@ export default function AppModal(props: IProps) {
|
||||
</div>
|
||||
{curTeamModal !== 'awel_layout' ? (
|
||||
<>
|
||||
<div className="mb-5">{t('Agents')}</div>
|
||||
<div className="mb-5">Agents</div>
|
||||
<Tabs addIcon={renderAddIcon()} type="editable-card" onChange={onChange} activeKey={activeKey} onEdit={onEdit} items={agents} />
|
||||
</>
|
||||
) : (
|
||||
|
@@ -31,7 +31,7 @@ export default function ResourceCard(props: IProps) {
|
||||
if (data) {
|
||||
setResourceValueOptions(
|
||||
data?.map((item) => {
|
||||
return { label: item, value: item };
|
||||
return { label: item.label, value: item.key + '' };
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
|
@@ -73,10 +73,10 @@ export const getVisAdvices = (props: { data: Datum[]; myChartAdvisor: Advisor; d
|
||||
*/
|
||||
const customDataProps = dataMetaMap
|
||||
? Object.keys(dataMetaMap).map((item) => {
|
||||
return { name: item, ...dataMetaMap[item] };
|
||||
})
|
||||
return { name: item, ...dataMetaMap[item] };
|
||||
})
|
||||
: null;
|
||||
|
||||
|
||||
// 可根据需要选择是否使用全部 fields 进行推荐
|
||||
const useAllFields = false;
|
||||
// 挑选出维值不只有一个的字段
|
||||
@@ -84,11 +84,11 @@ export const getVisAdvices = (props: { data: Datum[]; myChartAdvisor: Advisor; d
|
||||
const selectedFields =
|
||||
size(allFieldsInfo) > 2
|
||||
? allFieldsInfo?.filter((field) => {
|
||||
if (field.recommendation === 'string' || field.recommendation === 'date') {
|
||||
return field.distinct && field.distinct > 1;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
if (field.recommendation === 'string' || field.recommendation === 'date') {
|
||||
return field.distinct && field.distinct > 1;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
: allFieldsInfo;
|
||||
|
||||
const allAdvices = myChartAdvisor?.adviseWithLog({
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { hasSubset } from '../advisor/utils';
|
||||
import { findOrdinalField, processDateEncode, findNominalField, getLineSize, sortData } from './util';
|
||||
import { processDateEncode, findOrdinalField, findNominalField, getLineSize, sortData } from './util';
|
||||
import type { ChartKnowledge, CustomChart, GetChartConfigProps, Specification } from '../types';
|
||||
import type { Datum } from '@antv/ava';
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { hasSubset } from '../advisor/utils';
|
||||
import { findNominalField, findOrdinalField, getLineSize, processDateEncode, sortData } from './util';
|
||||
import { processDateEncode, findNominalField, findOrdinalField, getLineSize, sortData } from './util';
|
||||
import type { ChartKnowledge, CustomChart, GetChartConfigProps, Specification } from '../types';
|
||||
import { Datum } from '@antv/ava';
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type { Datum, FieldInfo } from "@antv/ava";
|
||||
import { hasSubset, intersects } from "../advisor/utils";
|
||||
import { hasSubset, intersects } from '../advisor/utils';
|
||||
import { cloneDeep, uniq } from "lodash";
|
||||
|
||||
/**
|
||||
|
@@ -3,7 +3,7 @@ import { Advice, Advisor, Datum } from '@antv/ava';
|
||||
import { Chart, ChartRef } from '@berryv/g2-react';
|
||||
import i18n, { I18nKeys } from '@/app/i18n';
|
||||
import { customizeAdvisor, getVisAdvices } from './advisor/pipeline';
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useContext, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { defaultAdvicesFilter } from './advisor/utils';
|
||||
import { AutoChartProps, ChartType, CustomAdvisorConfig, CustomChart, Specification } from './types';
|
||||
import { customCharts } from './charts';
|
||||
@@ -16,8 +16,7 @@ import { DownloadOutlined } from '@ant-design/icons';
|
||||
const { Option } = Select;
|
||||
|
||||
export const AutoChart = (props: AutoChartProps) => {
|
||||
const { chartType, scopeOfCharts, ruleConfig, data: originalData } = props;
|
||||
|
||||
const { data: originalData, chartType, scopeOfCharts, ruleConfig } = props;
|
||||
// 处理空值数据 (为'-'的数据)
|
||||
const data = processNilData(originalData) as Datum[];
|
||||
const { mode } = useContext(ChatContext);
|
||||
@@ -99,7 +98,7 @@ export const AutoChart = (props: AutoChartProps) => {
|
||||
// 处理 ava 内置折线图的排序问题
|
||||
const dataAnalyzerOutput = advisor?.dataAnalyzer.execute({ data })
|
||||
if (dataAnalyzerOutput && 'dataProps' in dataAnalyzerOutput) {
|
||||
spec.data = sortData({ data: spec.data, xField: dataAnalyzerOutput.dataProps?.find(field => field.recommendation === 'date'), chartType: chartTypeInput });
|
||||
spec.data = sortData({ data: spec.data, xField: dataAnalyzerOutput.dataProps?.find((field: any) => field.recommendation === 'date'), chartType: chartTypeInput });
|
||||
}
|
||||
}
|
||||
if (chartTypeInput === 'pie_chart' && spec?.encode?.color) {
|
||||
@@ -111,8 +110,8 @@ export const AutoChart = (props: AutoChartProps) => {
|
||||
key={chartTypeInput}
|
||||
options={{
|
||||
...spec,
|
||||
theme: mode,
|
||||
autoFit: true,
|
||||
theme: mode,
|
||||
height: 300,
|
||||
}}
|
||||
ref={chartRef}
|
||||
@@ -120,7 +119,7 @@ export const AutoChart = (props: AutoChartProps) => {
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [advices, renderChartType]);
|
||||
}, [advices, mode, renderChartType]);
|
||||
|
||||
if (renderChartType) {
|
||||
return (
|
||||
@@ -159,7 +158,7 @@ export const AutoChart = (props: AutoChartProps) => {
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="auto-chart-content">{visComponent}</div>
|
||||
<div className="flex">{visComponent}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ type Props = {
|
||||
};
|
||||
|
||||
function Chart({ chartsData }: Props) {
|
||||
console.log(chartsData,'xxx')
|
||||
const chartRows = useMemo(() => {
|
||||
if (chartsData) {
|
||||
let res = [];
|
||||
@@ -66,7 +67,7 @@ function Chart({ chartsData }: Props) {
|
||||
return <LineChart key={chart.chart_uid} chart={chart} />;
|
||||
} else if (chart.chart_type === 'BarChart' || chart.type === 'BarChart') {
|
||||
return <BarChart key={chart.chart_uid} chart={chart} />;
|
||||
} else if (chart.chart_type === 'Table' || chart.type === 'Table') {
|
||||
} else if (chart.chart_type === 'Table' || chart.type === 'TableChartData') {
|
||||
return <TableChart key={chart.chart_uid} chart={chart} />;
|
||||
}
|
||||
})}
|
||||
|
@@ -11,7 +11,7 @@ interface Props {
|
||||
}
|
||||
|
||||
function formatMarkdownVal(val: string) {
|
||||
return val.replace(/<table(\w*=[^>]+)>/gi, '<table $1>').replace(/<tr(\w*=[^>]+)>/gi, '<tr $1>');
|
||||
return val?.replace(/<table(\w*=[^>]+)>/gi, '<table $1>').replace(/<tr(\w*=[^>]+)>/gi, '<tr $1>');
|
||||
}
|
||||
|
||||
function AgentContent({ content }: Props) {
|
||||
|
@@ -16,7 +16,7 @@ import MyEmpty from '../common/MyEmpty';
|
||||
const ChatContainer = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const { scene, chatId, model, agent, setModel, history, setHistory } = useContext(ChatContext);
|
||||
const chat = useChat({});
|
||||
const { chat } = useChat({});
|
||||
const initMessage = (searchParams && searchParams.get('initMessage')) ?? '';
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
@@ -34,9 +34,11 @@ const ChatContainer = () => {
|
||||
if (contextTemp) {
|
||||
try {
|
||||
const contextObj = typeof contextTemp === 'string' ? JSON.parse(contextTemp) : contextTemp;
|
||||
console.log('contextObj', contextObj);
|
||||
setChartsData(contextObj?.template_name === 'report' ? contextObj?.charts : undefined);
|
||||
} catch (e) {
|
||||
setChartsData(undefined);
|
||||
console.log(e);
|
||||
setChartsData([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -117,7 +119,9 @@ const ChatContainer = () => {
|
||||
<Chart chartsData={chartsData} />
|
||||
</div>
|
||||
)}
|
||||
{!chartsData?.length && scene === 'chat_dashboard' && <MyEmpty className="w-full xl:w-3/4 h-1/2 xl:h-full" />}
|
||||
{!chartsData?.length && scene === 'chat_dashboard' && (
|
||||
<MyEmpty className="w-full xl:w-3/4 h-1/2 xl:h-full" />
|
||||
)}
|
||||
{/** chat panel */}
|
||||
<div
|
||||
className={classNames('flex flex-1 flex-col overflow-hidden', {
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import ModelIcon from '@/ant-components/chat/content/ModelIcon';
|
||||
import { LinkOutlined, SwapRightOutlined } from '@ant-design/icons';
|
||||
import { Popover, Space } from 'antd';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import markdownComponents from './config';
|
||||
import { renderModelIcon } from '../header/model-selector';
|
||||
import { SwapRightOutlined } from '@ant-design/icons';
|
||||
import ReferencesContent from './ReferencesContent';
|
||||
|
||||
interface Props {
|
||||
data: {
|
||||
@@ -9,27 +13,30 @@ interface Props {
|
||||
receiver: string;
|
||||
model: string | null;
|
||||
markdown: string;
|
||||
resource: any;
|
||||
}[];
|
||||
}
|
||||
|
||||
function AgentMessages({ data }: Props) {
|
||||
if (!data || !data.length) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.map((item, index) => (
|
||||
<div key={index} className="rounded my-4 md:my-6">
|
||||
<div key={index} className="rounded">
|
||||
<div className="flex items-center mb-3 text-sm">
|
||||
{item.model ? renderModelIcon(item.model) : <div className="rounded-full w-6 h-6 bg-gray-100" />}
|
||||
{item.model ? <ModelIcon model={item.model} /> : <div className="rounded-full w-6 h-6 bg-gray-100" />}
|
||||
<div className="ml-2 opacity-70">
|
||||
{item.sender}
|
||||
<SwapRightOutlined className="mx-2 text-base" />
|
||||
{item.receiver}
|
||||
</div>
|
||||
</div>
|
||||
<div className="whitespace-normal text-sm">
|
||||
<ReactMarkdown components={markdownComponents}>{item.markdown}</ReactMarkdown>
|
||||
<div className="whitespace-normal text-sm mb-3">
|
||||
<ReactMarkdown components={markdownComponents} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
|
||||
{item.markdown}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
{item.resource && item.resource !== 'null' && <ReferencesContent references={item.resource} />}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
|
@@ -1,6 +1,9 @@
|
||||
import { CaretRightOutlined, CheckOutlined, ClockCircleOutlined } from '@ant-design/icons';
|
||||
import { Collapse } from 'antd';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
import markdownComponents from './config';
|
||||
|
||||
interface Props {
|
||||
@@ -25,7 +28,7 @@ function AgentPlans({ data }: Props) {
|
||||
return {
|
||||
key: index,
|
||||
label: (
|
||||
<div className="whitespace-normal">
|
||||
<div>
|
||||
<span>
|
||||
{item.name} - {item.agent}
|
||||
</span>
|
||||
@@ -36,7 +39,11 @@ function AgentPlans({ data }: Props) {
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
children: <ReactMarkdown components={markdownComponents}>{item.markdown}</ReactMarkdown>,
|
||||
children: (
|
||||
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
|
||||
{item.markdown}
|
||||
</ReactMarkdown>
|
||||
),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
|
@@ -20,11 +20,10 @@ function ChartView({ data, type, sql }: { data: Datum[]; type: BackEndChartType;
|
||||
label: 'Chart',
|
||||
children: <AutoChart data={data} chartType={getChartType(type)} />,
|
||||
};
|
||||
|
||||
const SqlItem = {
|
||||
key: 'sql',
|
||||
label: 'SQL',
|
||||
children: <CodePreview language="sql" code={formatSql(sql)} />,
|
||||
children: <CodePreview language="sql" code={formatSql(sql ?? '', 'mysql') as string} />,
|
||||
};
|
||||
const DataItem = {
|
||||
key: 'data',
|
||||
|
@@ -25,7 +25,7 @@ export function CodePreview({ code, light, dark, language, customStyle }: Props)
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => {
|
||||
const success = copy(code);
|
||||
message[success ? 'success' : 'error'](success ? 'Copy success' : 'Copy failed');
|
||||
message[success ? 'success' : 'error'](success ? '复制成功' : '复制失败');
|
||||
}}
|
||||
/>
|
||||
<SyntaxHighlighter customStyle={customStyle} language={language} style={mode === 'dark' ? dark ?? coldarkDark : light ?? oneDark}>
|
||||
|
@@ -1,19 +1,23 @@
|
||||
import { LinkOutlined, ReadOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Table, Image, Tag, Tabs, TabsProps, Popover } from 'antd';
|
||||
import { Reference } from '@/types/chat';
|
||||
import { AutoChart, BackEndChartType, getChartType } from '@/components/chart';
|
||||
import { CodePreview } from './code-preview';
|
||||
import { LinkOutlined, ReadOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { Datum } from '@antv/ava';
|
||||
import { Image, Table, Tabs, TabsProps, Tag } from 'antd';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { IChunk } from '@/types/knowledge';
|
||||
import AgentPlans from './agent-plans';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
import AgentMessages from './agent-messages';
|
||||
import VisConvertError from './vis-convert-error';
|
||||
import AgentPlans from './agent-plans';
|
||||
import { CodePreview } from './code-preview';
|
||||
import ReferencesContent from './ReferencesContent';
|
||||
import VisChart from './vis-chart';
|
||||
import VisCode from './vis-code';
|
||||
import VisConvertError from './vis-convert-error';
|
||||
import VisDashboard from './vis-dashboard';
|
||||
import VisPlugin from './vis-plugin';
|
||||
import VisCode from './vis-code';
|
||||
import VisAppLink from './VisAppLink';
|
||||
import VisChatLink from './VisChatLink';
|
||||
import VisResponse from './VisResponse';
|
||||
import { formatSql } from '@/utils';
|
||||
|
||||
type MarkdownComponent = Parameters<typeof ReactMarkdown>['0']['components'];
|
||||
@@ -106,6 +110,22 @@ const basicComponents: MarkdownComponent = {
|
||||
}
|
||||
}
|
||||
|
||||
if (lang === 'vis-app-link') {
|
||||
try {
|
||||
const data = JSON.parse(content) as Parameters<typeof VisAppLink>[0]['data'];
|
||||
return <VisAppLink data={data} />;
|
||||
} catch (e) {
|
||||
return <CodePreview language={lang} code={content} />;
|
||||
}
|
||||
}
|
||||
if (lang === 'vis-api-response') {
|
||||
try {
|
||||
const data = JSON.parse(content) as Parameters<typeof VisResponse>[0]['data'];
|
||||
return <VisResponse data={data} />;
|
||||
} catch (e) {
|
||||
return <CodePreview language={lang} code={content} />;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{!inline ? (
|
||||
@@ -115,7 +135,7 @@ const basicComponents: MarkdownComponent = {
|
||||
{children}
|
||||
</code>
|
||||
)}
|
||||
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]}>
|
||||
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
|
||||
{matchValues.join('\n')}
|
||||
</ReactMarkdown>
|
||||
</>
|
||||
@@ -131,9 +151,7 @@ const basicComponents: MarkdownComponent = {
|
||||
return <li className={`text-sm leading-7 ml-5 pl-2 text-gray-600 dark:text-gray-300 ${ordered ? 'list-decimal' : 'list-disc'}`}>{children}</li>;
|
||||
},
|
||||
table({ children }) {
|
||||
return (
|
||||
<table className="my-2 rounded-tl-md rounded-tr-md max-w-full bg-white dark:bg-gray-800 text-sm rounded-lg overflow-hidden">{children}</table>
|
||||
);
|
||||
return <table className="my-2 rounded-tl-md rounded-tr-md bg-white dark:bg-gray-800 text-sm rounded-lg overflow-hidden">{children}</table>;
|
||||
},
|
||||
thead({ children }) {
|
||||
return <thead className="bg-[#fafafa] dark:bg-black font-semibold">{children}</thead>;
|
||||
@@ -178,7 +196,7 @@ const basicComponents: MarkdownComponent = {
|
||||
Image Loading...
|
||||
</Tag>
|
||||
}
|
||||
fallback="/images/fallback.png"
|
||||
fallback="/pictures/fallback.png"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -190,6 +208,43 @@ const basicComponents: MarkdownComponent = {
|
||||
</blockquote>
|
||||
);
|
||||
},
|
||||
button({ children, className, ...restProps }) {
|
||||
if (className === 'chat-link') {
|
||||
const msg = (restProps as any)?.['data-msg'];
|
||||
return <VisChatLink msg={msg}>{children}</VisChatLink>;
|
||||
}
|
||||
return (
|
||||
<button className={className} {...restProps}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const returnSqlVal = (val: string) => {
|
||||
const punctuationMap: any = {
|
||||
',': ',',
|
||||
'。': '.',
|
||||
'?': '?',
|
||||
'!': '!',
|
||||
':': ':',
|
||||
';': ';',
|
||||
'“': '"',
|
||||
'”': '"',
|
||||
'‘': "'",
|
||||
'’': "'",
|
||||
'(': '(',
|
||||
')': ')',
|
||||
'【': '[',
|
||||
'】': ']',
|
||||
'《': '<',
|
||||
'》': '>',
|
||||
'—': '-',
|
||||
'、': ',',
|
||||
'…': '...',
|
||||
};
|
||||
const regex = new RegExp(Object.keys(punctuationMap).join('|'), 'g');
|
||||
return val.replace(regex, (match) => punctuationMap[match]);
|
||||
};
|
||||
|
||||
const extraComponents: MarkdownComponent = {
|
||||
@@ -228,12 +283,12 @@ const extraComponents: MarkdownComponent = {
|
||||
const SqlItem = {
|
||||
key: 'sql',
|
||||
label: 'SQL',
|
||||
children: <CodePreview code={formatSql(data?.sql, 'mysql')} language="sql" />,
|
||||
children: <CodePreview code={formatSql(returnSqlVal(data?.sql), 'mysql') as string} language={'sql'} />,
|
||||
};
|
||||
const DataItem = {
|
||||
key: 'data',
|
||||
label: 'Data',
|
||||
children: <Table dataSource={data?.data} columns={columns} scroll={{x:true}} />,
|
||||
children: <Table dataSource={data?.data} columns={columns} scroll={{x:true}} virtual={true} />,
|
||||
};
|
||||
const TabItems: TabsProps['items'] = data?.type === 'response_table' ? [DataItem, SqlItem] : [ChartItem, SqlItem, DataItem];
|
||||
|
||||
@@ -245,71 +300,15 @@ const extraComponents: MarkdownComponent = {
|
||||
);
|
||||
},
|
||||
references: function ({ title, references, children }) {
|
||||
let referenceData;
|
||||
// Low version compatibility, read data from children
|
||||
if (children) {
|
||||
try {
|
||||
referenceData = JSON.parse(children as string);
|
||||
title = referenceData.title;
|
||||
references = referenceData.references;
|
||||
const referenceData = JSON.parse(children as string);
|
||||
const references = referenceData.references;
|
||||
return <ReferencesContent references={references} />;
|
||||
} catch (error) {
|
||||
console.log('parse references failed', error);
|
||||
return <p className="text-sm text-red-500">Render Reference Error!</p>;
|
||||
}
|
||||
} else {
|
||||
// new version, read from tag props.
|
||||
try {
|
||||
references = JSON.parse(references as string);
|
||||
} catch (error) {
|
||||
console.log('parse references failed', error);
|
||||
return <p className="text-sm text-red-500">Render Reference Error!</p>;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!references || references?.length < 1) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="border-t-[1px] border-gray-300 mt-3 py-2">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
||||
<LinkOutlined className="mr-2" />
|
||||
<span className="font-semibold">{title}</span>
|
||||
</p>
|
||||
{references.map((reference: Reference, index: number) => (
|
||||
<div key={`file_${index}`} className="text-sm font-normal block ml-2 h-6 leading-6 overflow-hidden">
|
||||
<span className="inline-block w-6">[{index + 1}]</span>
|
||||
<span className="mr-2 lg:mr-4 text-blue-400">{reference.name}</span>
|
||||
{reference?.chunks?.map((chunk: IChunk | number, index) => (
|
||||
<span key={`chunk_${index}`}>
|
||||
{typeof chunk === 'object' ? (
|
||||
<Popover
|
||||
content={
|
||||
<div className="max-w-4xl">
|
||||
<p className="mt-2 font-bold mr-2 border-t border-gray-500 pt-2">Content:</p>
|
||||
<p>{chunk?.content || 'No Content'}</p>
|
||||
<p className="mt-2 font-bold mr-2 border-t border-gray-500 pt-2">MetaData:</p>
|
||||
<p>{chunk?.meta_info || 'No MetaData'}</p>
|
||||
<p className="mt-2 font-bold mr-2 border-t border-gray-500 pt-2">Score:</p>
|
||||
<p>{chunk?.recall_score || ''}</p>
|
||||
</div>
|
||||
}
|
||||
title="Chunk Information"
|
||||
>
|
||||
<span className="cursor-pointer text-blue-500 ml-2" key={`chunk_content_${chunk?.id}`}>
|
||||
{chunk?.id}
|
||||
</span>
|
||||
</Popover>
|
||||
) : (
|
||||
<span className="cursor-pointer text-blue-500 ml-2" key={`chunk_id_${chunk}`}>
|
||||
{chunk}
|
||||
</span>
|
||||
)}
|
||||
{index < reference?.chunks.length - 1 && <span key={`chunk_comma_${index}`}>,</span>}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
summary: function ({ children }) {
|
||||
return (
|
||||
|
@@ -13,7 +13,10 @@ interface Props {
|
||||
}
|
||||
|
||||
function VisChart({ data }: Props) {
|
||||
return <ChartView data={data.data} type={data.type} sql={data.sql} />;
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
return <ChartView data={data?.data} type={data?.type} sql={data?.sql} />;
|
||||
}
|
||||
|
||||
export default VisChart;
|
||||
|
@@ -2,8 +2,10 @@ import { CheckOutlined, ClockCircleOutlined, CloseOutlined, LoadingOutlined } fr
|
||||
import classNames from 'classnames';
|
||||
import { ReactNode } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import markdownComponents from './config';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
import markdownComponents from './config';
|
||||
|
||||
interface IVisPlugin {
|
||||
name: string;
|
||||
@@ -42,15 +44,16 @@ const pluginViewStatusMapper: Record<IVisPlugin['status'], { bgClass: string; ic
|
||||
function VisPlugin({ data }: Props) {
|
||||
const { bgClass, icon } = pluginViewStatusMapper[data.status] ?? {};
|
||||
|
||||
|
||||
return (
|
||||
<div className="bg-theme-light dark:bg-theme-dark-container rounded overflow-hidden my-2 flex flex-col lg:max-w-[80%]">
|
||||
<div className="bg-theme-light dark:bg-theme-dark-container rounded overflow-hidden my-2 flex flex-col">
|
||||
<div className={classNames('flex px-4 md:px-6 py-2 items-center text-white text-sm', bgClass)}>
|
||||
{data.name}
|
||||
{icon}
|
||||
</div>
|
||||
{data.result ? (
|
||||
<div className="px-4 md:px-6 py-4 text-sm whitespace-normal">
|
||||
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]}>
|
||||
<ReactMarkdown components={markdownComponents} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
|
||||
{data.result ?? ''}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
@@ -1,25 +1,25 @@
|
||||
import { useState, useRef, useEffect, useMemo, useContext } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import MonacoEditor from './monaco-editor';
|
||||
import ChatContent from './chat-content';
|
||||
import ChatFeedback from './chat-feedback';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { FeedBack, IChatDialogueMessageSchema } from '@/types/chat';
|
||||
import classNames from 'classnames';
|
||||
import { Modal, message, Tooltip } from 'antd';
|
||||
import { renderModelIcon } from './header/model-selector';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CompletionInput from '../common/completion-input';
|
||||
import { useAsyncEffect } from 'ahooks';
|
||||
import { STORAGE_INIT_MESSAGE_KET } from '@/utils';
|
||||
import { Button, IconButton } from '@mui/joy';
|
||||
import { CopyOutlined, RedoOutlined } from '@ant-design/icons';
|
||||
import { getInitMessage } from '@/utils';
|
||||
import { apiInterceptors, getChatFeedBackSelect } from '@/client/api';
|
||||
import useSummary from '@/hooks/use-summary';
|
||||
import { FeedBack, IChatDialogueMessageSchema } from '@/types/chat';
|
||||
import { STORAGE_INIT_MESSAGE_KET, getInitMessage } from '@/utils';
|
||||
import { CopyOutlined, RedoOutlined } from '@ant-design/icons';
|
||||
import { Button, IconButton } from '@mui/joy';
|
||||
import { useAsyncEffect } from 'ahooks';
|
||||
import { Modal, Tooltip, message } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import CompletionInput from '../common/completion-input';
|
||||
import AgentContent from './agent-content';
|
||||
import ChatContent from './chat-content';
|
||||
import ChatFeedback from './chat-feedback';
|
||||
import { renderModelIcon } from './header/model-selector';
|
||||
import MonacoEditor from './monaco-editor';
|
||||
import MyEmpty from '../common/MyEmpty';
|
||||
|
||||
type Props = {
|
||||
@@ -93,12 +93,12 @@ const Completion = ({ messages, onSubmit }: Props) => {
|
||||
const result = copy(pureStr);
|
||||
if (result) {
|
||||
if (pureStr) {
|
||||
messageApi.open({ type: 'success', content: t('Copy_success') });
|
||||
messageApi.open({ type: 'success', content: t('copy_success') });
|
||||
} else {
|
||||
messageApi.open({ type: 'warning', content: t('Copy_nothing') });
|
||||
messageApi.open({ type: 'warning', content: t('copy_nothing') });
|
||||
}
|
||||
} else {
|
||||
messageApi.open({ type: 'error', content: t('Copry_error') });
|
||||
messageApi.open({ type: 'error', content: t('copy_failed') });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -184,7 +184,7 @@ const Completion = ({ messages, onSubmit }: Props) => {
|
||||
question={showMessages?.filter((e) => e?.role === 'human' && e?.order === content.order)[0]?.context}
|
||||
knowledge_space={spaceNameOriginal || dbParam || ''}
|
||||
/>
|
||||
<Tooltip title={t('Copy')}>
|
||||
<Tooltip title={t('copy')}>
|
||||
<Button
|
||||
onClick={() => onCopyContext(content?.context)}
|
||||
slots={{ root: IconButton }}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import React, { ChangeEvent, Key, useEffect, useMemo, useState } from 'react';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Button, Select, Table, Tooltip } from 'antd';
|
||||
import { Input, Tree } from 'antd';
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Input, Tree } from 'antd';
|
||||
import type { DataNode } from 'antd/es/tree';
|
||||
import MonacoEditor, { ISession } from './monaco-editor';
|
||||
import { sendGetRequest, sendSpacePostRequest } from '@/utils/request';
|
||||
@@ -10,6 +10,7 @@ import { useSearchParams } from 'next/navigation';
|
||||
import { OnChange } from '@monaco-editor/react';
|
||||
import Header from './header';
|
||||
import Chart from '../chart';
|
||||
|
||||
import { CaretRightOutlined, LeftOutlined, RightOutlined, SaveFilled } from '@ant-design/icons';
|
||||
import { ColumnType } from 'antd/es/table';
|
||||
import Database from '../icons/database';
|
||||
@@ -62,7 +63,6 @@ interface ITableTreeItem {
|
||||
function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, tables, handleChange }: IProps) {
|
||||
const chartWrapper = useMemo(() => {
|
||||
if (!chartData) return null;
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-auto p-2" style={{ flexShrink: 0, overflow: 'hidden' }}>
|
||||
<Chart chartsData={[chartData]} />
|
||||
@@ -87,7 +87,6 @@ function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, tab
|
||||
return {
|
||||
columns: tbCols,
|
||||
dataSource: tbDatas,
|
||||
|
||||
};
|
||||
}, [tableData]);
|
||||
const session: ISession = useMemo(() => {
|
||||
@@ -103,13 +102,13 @@ function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, tab
|
||||
})
|
||||
});
|
||||
return {
|
||||
async getTableList(schemaName) {
|
||||
async getTableList(schemaName: any) {
|
||||
if (schemaName && schemaName!== db?.title) {
|
||||
return [];
|
||||
}
|
||||
return tableList?.map((table: ITableTreeItem) => table.title) || [];
|
||||
},
|
||||
async getTableColumns(tableName) {
|
||||
async getTableColumns(tableName: any) {
|
||||
return map[tableName] || [];
|
||||
},
|
||||
async getSchemaList() {
|
||||
@@ -117,6 +116,7 @@ function DbEditorContent({ layout = 'LR', editorValue, chartData, tableData, tab
|
||||
}
|
||||
};
|
||||
}, [tables])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('flex w-full flex-1 h-full gap-2 overflow-hidden', {
|
||||
@@ -146,7 +146,7 @@ function DbEditor() {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [currentRound, setCurrentRound] = useState<null | string | number>();
|
||||
const [autoExpandParent, setAutoExpandParent] = useState(true);
|
||||
const [chartData, setChartData] = useState();
|
||||
const [chartData, setChartData] = useState<any>();
|
||||
const [editorValue, setEditorValue] = useState<EditorValueProps | EditorValueProps[]>();
|
||||
const [newEditorValue, setNewEditorValue] = useState<EditorValueProps>();
|
||||
const [tableData, setTableData] = useState<{ columns: string[]; values: (string | number)[] }>();
|
||||
@@ -175,7 +175,7 @@ function DbEditor() {
|
||||
|
||||
const { run: runSql, loading: runLoading } = useRequest(
|
||||
async () => {
|
||||
const db_name = rounds?.data?.find((item) => item.round === currentRound)?.db_name;
|
||||
const db_name = rounds?.data?.find((item: any) => item.round === currentRound)?.db_name;
|
||||
return await sendSpacePostRequest(`/api/v1/editor/sql/run`, {
|
||||
db_name,
|
||||
sql: newEditorValue?.sql,
|
||||
@@ -194,7 +194,7 @@ function DbEditor() {
|
||||
|
||||
const { run: runCharts, loading: runChartsLoading } = useRequest(
|
||||
async () => {
|
||||
const db_name = rounds?.data?.find((item) => item.round === currentRound)?.db_name;
|
||||
const db_name = rounds?.data?.find((item: any) => item.round === currentRound)?.db_name;
|
||||
const params: {
|
||||
db_name: string;
|
||||
sql?: string;
|
||||
@@ -257,7 +257,7 @@ function DbEditor() {
|
||||
|
||||
const { run: submitChart, loading: submitChartLoading } = useRequest(
|
||||
async () => {
|
||||
const db_name = rounds?.data?.find((item) => item.round === currentRound)?.db_name;
|
||||
const db_name = rounds?.data?.find((item: any) => item.round === currentRound)?.db_name;
|
||||
return await sendSpacePostRequest(`/api/v1/chart/editor/submit`, {
|
||||
conv_uid: id,
|
||||
chart_title: newEditorValue?.title,
|
||||
@@ -365,7 +365,6 @@ function DbEditor() {
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
if (item.children) {
|
||||
const itemKey = parentKey ? String(parentKey) + '_' + item.key : item.key;
|
||||
return { title: strTitle, showTitle, key: itemKey, children: loop(item.children, itemKey) };
|
||||
@@ -392,7 +391,6 @@ function DbEditor() {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const node = data[i];
|
||||
const { key, title } = node;
|
||||
|
||||
res.push({ key, title: title as string, parentKey });
|
||||
if (node.children) {
|
||||
generateList(node.children, key);
|
||||
@@ -477,8 +475,7 @@ function DbEditor() {
|
||||
<div className="flex flex-col w-full h-full overflow-hidden">
|
||||
<Header />
|
||||
<div className="relative flex flex-1 p-4 pt-0 overflow-hidden">
|
||||
{/* Database Tree Node */}
|
||||
<div className="group/side relative mr-4">
|
||||
<div className="relative flex overflow-hidden mr-4">
|
||||
<div
|
||||
className={classNames('h-full relative transition-[width] overflow-hidden', {
|
||||
'w-0': isMenuExpand,
|
||||
@@ -621,7 +618,6 @@ function DbEditor() {
|
||||
})}
|
||||
>
|
||||
<DbEditorContent
|
||||
layout={layout}
|
||||
editorValue={item}
|
||||
handleChange={(value) => {
|
||||
const { sql, thoughts } = resolveSqlAndThoughts(value);
|
||||
|
@@ -101,7 +101,7 @@ function ExcelUpload({ convUid, chatMode, onComplete, ...props }: PropsWithChild
|
||||
{loading ? (percent === 100 ? 'Analysis' : 'Uploading') : 'Upload'}
|
||||
</Button>
|
||||
{!!fileList.length && (
|
||||
<div className="mt-2 text-gray-500 text-sm flex items-center">
|
||||
<div className="mt-2 text-gray-500 text-sm flex items-center" onClick={() => setFileList([])}>
|
||||
<LinkOutlined className="mr-2" />
|
||||
<span>{fileList[0]?.name}</span>
|
||||
</div>
|
||||
|
@@ -3,8 +3,8 @@
|
||||
*/
|
||||
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { Select } from 'antd';
|
||||
import { MODEL_ICON_MAP } from '@/utils/constants';
|
||||
import { Select } from 'antd';
|
||||
import Image from 'next/image';
|
||||
import { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -26,6 +26,7 @@ export function renderModelIcon(model?: string, props?: { width: number; height:
|
||||
width={width || 24}
|
||||
height={height || 24}
|
||||
src={MODEL_ICON_MAP[model]?.icon || DEFAULT_ICON_URL}
|
||||
key={MODEL_ICON_MAP[model]?.icon || DEFAULT_ICON_URL}
|
||||
alt="llm"
|
||||
/>
|
||||
);
|
||||
|
@@ -2,10 +2,10 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
|
||||
import Editor, { OnChange, loader } from '@monaco-editor/react';
|
||||
import classNames from 'classnames';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { formatSql } from '@/utils';
|
||||
import { getModelService } from './ob-editor/service';
|
||||
import { useLatest } from 'ahooks';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { github, githubDark } from './ob-editor/theme';
|
||||
import { register } from './ob-editor/ob-plugin';
|
||||
|
||||
@@ -24,7 +24,6 @@ interface MonacoEditorProps {
|
||||
onChange?: OnChange;
|
||||
thoughts?: string;
|
||||
session?: ISession;
|
||||
|
||||
}
|
||||
|
||||
let plugin = null;
|
||||
@@ -33,6 +32,7 @@ monaco.editor.defineTheme('githubDark', githubDark as any);
|
||||
|
||||
export default function MonacoEditor({ className, value, language = 'mysql', onChange, thoughts, session }: MonacoEditorProps) {
|
||||
// merge value and thoughts
|
||||
|
||||
const editorValue = useMemo(() => {
|
||||
if (language !== 'mysql') {
|
||||
return value;
|
||||
@@ -58,7 +58,6 @@ export default function MonacoEditor({ className, value, language = 'mysql', onC
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Editor
|
||||
className={classNames(className)}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ interface Props {
|
||||
}
|
||||
|
||||
function ChatDialog({ title, chatMode, completionApi, chatParams, model = '' }: Props) {
|
||||
const chat = useChat({ queryAgentURL: completionApi });
|
||||
const { chat } = useChat({ queryAgentURL: completionApi });
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [list, setList] = useState<IChatDialogueMessageSchema[]>([]);
|
||||
|
@@ -1,22 +1,23 @@
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { apiInterceptors, getDocumentList } from '@/client/api';
|
||||
import { IDocument } from '@/types/knowledge';
|
||||
import { SendOutlined } from '@ant-design/icons';
|
||||
import { Button, Input } from 'antd';
|
||||
import { PropsWithChildren, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import PromptBot from './prompt-bot';
|
||||
import DocUpload from '../chat/doc-upload';
|
||||
import DocList from '../chat/doc-list';
|
||||
import { IDocument } from '@/types/knowledge';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { apiInterceptors, getDocumentList } from '@/client/api';
|
||||
import DocUpload from '../chat/doc-upload';
|
||||
import PromptBot from './prompt-bot';
|
||||
|
||||
type TextAreaProps = Omit<Parameters<typeof Input.TextArea>[0], 'value' | 'onPressEnter' | 'onChange' | 'onSubmit'>;
|
||||
|
||||
interface Props {
|
||||
loading?: boolean;
|
||||
onSubmit: (val: string) => void;
|
||||
handleFinish?: (val: boolean) => void;
|
||||
loading?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
function CompletionInput({ children, loading, onSubmit, handleFinish, ...props }: PropsWithChildren<Props & TextAreaProps>) {
|
||||
function CompletionInput({ children, loading, onSubmit, handleFinish, placeholder, ...props }: PropsWithChildren<Props & TextAreaProps>) {
|
||||
const { dbParam, scene } = useContext(ChatContext);
|
||||
|
||||
const [userInput, setUserInput] = useState('');
|
||||
@@ -77,6 +78,7 @@ function CompletionInput({ children, loading, onSubmit, handleFinish, ...props }
|
||||
}
|
||||
setUserInput(e.target.value);
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<Button
|
||||
className="ml-2 flex items-center justify-center absolute right-0 bottom-0"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { HtmlHTMLAttributes, PropsWithChildren, ReactNode, memo, useCallback, useMemo } from 'react';
|
||||
import { Tag, TagProps, Tooltip } from 'antd';
|
||||
import { Popover, Tag, TagProps, Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import Image from 'next/image';
|
||||
|
||||
@@ -25,6 +25,7 @@ interface Props {
|
||||
icon?: ReactNode;
|
||||
iconBorder?: boolean;
|
||||
onClick?: () => void;
|
||||
extraContent?: ReactNode;
|
||||
}
|
||||
|
||||
function GPTCard({
|
||||
@@ -37,6 +38,7 @@ function GPTCard({
|
||||
disabled,
|
||||
operations,
|
||||
className,
|
||||
extraContent,
|
||||
...props
|
||||
}: PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & Props>) {
|
||||
const iconNode = useMemo(() => {
|
||||
@@ -93,11 +95,14 @@ function GPTCard({
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className="p-4 0">
|
||||
<div className="flex items-center">
|
||||
{iconNode}
|
||||
<div className="flex flex-col">
|
||||
<h2 className="text-sm font-semibold">{title}</h2>
|
||||
<Popover title={title}>
|
||||
<h2 className="text-sm font-semibold line-clamp-1 pr-8">{title}</h2>
|
||||
</Popover>
|
||||
|
||||
{tagNode}
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,6 +133,7 @@ function GPTCard({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute top-2 right-4 ">{extraContent}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { Button, Form, Input, InputNumber, Modal, Select, message } from 'antd';
|
||||
import { Button, Form, Input, InputNumber, Modal, Select, Spin, Tooltip, message } from 'antd';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { apiInterceptors, postDbAdd, postDbEdit, postDbTestConnect } from '@/client/api';
|
||||
import { addOmcDB, apiInterceptors, getSupportDBList, postDbAdd, postDbEdit, postDbTestConnect } from '@/client/api';
|
||||
import { DBOption, DBType, DbListResponse, PostDbParams } from '@/types/db';
|
||||
import { isFileDb } from '@/pages/database';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDebounceFn } from 'ahooks';
|
||||
|
||||
type DBItem = DbListResponse[0];
|
||||
type DBItem = DbListResponse[0] & { db_arn?: string };
|
||||
|
||||
interface Props {
|
||||
dbTypeList: DBOption[];
|
||||
@@ -23,7 +24,8 @@ function FormDialog({ open, choiceDBType, dbTypeList, editValue, dbNames, onClos
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm<DBItem>();
|
||||
const dbType = Form.useWatch('db_type', form);
|
||||
|
||||
const [omcDBList, setOmcDBList] = useState([]);
|
||||
const [omcListLoading, setOmcListLoading] = useState(false);
|
||||
const fileDb = useMemo(() => isFileDb(dbTypeList, dbType), [dbTypeList, dbType]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -35,6 +37,9 @@ function FormDialog({ open, choiceDBType, dbTypeList, editValue, dbNames, onClos
|
||||
useEffect(() => {
|
||||
if (editValue) {
|
||||
form.setFieldsValue({ ...editValue });
|
||||
if (editValue.db_type === 'omc') {
|
||||
form.setFieldValue('db_arn', editValue.db_path);
|
||||
}
|
||||
}
|
||||
}, [editValue]);
|
||||
|
||||
@@ -45,7 +50,33 @@ function FormDialog({ open, choiceDBType, dbTypeList, editValue, dbNames, onClos
|
||||
}, [open]);
|
||||
|
||||
const onFinish = async (val: DBItem) => {
|
||||
const { db_host, db_path, db_port, ...params } = val;
|
||||
const { db_host, db_path, db_port, db_type, ...params } = val;
|
||||
setLoading(true);
|
||||
|
||||
if (db_type === 'omc') {
|
||||
const item = omcDBList?.find((item: any) => item.arn === val.db_name) as any;
|
||||
|
||||
try {
|
||||
const [err] = await apiInterceptors(
|
||||
addOmcDB({
|
||||
db_type: 'omc',
|
||||
file_path: val.db_arn || '',
|
||||
comment: val.comment,
|
||||
db_name: item?.dbName || val.db_name,
|
||||
}),
|
||||
);
|
||||
if (err) {
|
||||
message.error(err.message);
|
||||
return;
|
||||
}
|
||||
message.success('success');
|
||||
onSuccess?.();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (!editValue && dbNames.some((item) => item === params.db_name)) {
|
||||
message.error('The database already exists!');
|
||||
return;
|
||||
@@ -53,10 +84,10 @@ function FormDialog({ open, choiceDBType, dbTypeList, editValue, dbNames, onClos
|
||||
const data: PostDbParams = {
|
||||
db_host: fileDb ? undefined : db_host,
|
||||
db_port: fileDb ? undefined : db_port,
|
||||
db_type: db_type,
|
||||
file_path: fileDb ? db_path : undefined,
|
||||
...params,
|
||||
};
|
||||
setLoading(true);
|
||||
try {
|
||||
const [testErr] = await apiInterceptors(postDbTestConnect(data));
|
||||
if (testErr) return;
|
||||
@@ -73,24 +104,78 @@ function FormDialog({ open, choiceDBType, dbTypeList, editValue, dbNames, onClos
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
console.log(form.getFieldValue('db_type'));
|
||||
const { run: fetchOmcList } = useDebounceFn(
|
||||
async (name: string) => {
|
||||
setOmcListLoading(true);
|
||||
const [error, data = []] = (await apiInterceptors(getSupportDBList(name))) as any;
|
||||
setOmcListLoading(false);
|
||||
|
||||
setOmcDBList(data.map((item: any) => ({ ...item, label: item.dbName, value: item.arn })));
|
||||
},
|
||||
{
|
||||
wait: 500,
|
||||
},
|
||||
);
|
||||
console.log('omcDBList', omcDBList);
|
||||
|
||||
const lockDBType = useMemo(() => !!editValue || !!choiceDBType, [editValue, choiceDBType]);
|
||||
|
||||
return (
|
||||
<Modal open={open} width={400} title={editValue ? t('Edit') : t('create_database')} maskClosable={false} footer={null} onCancel={onClose}>
|
||||
<Form form={form} className="pt-2" labelCol={{ span: 6 }} labelAlign="left" onFinish={onFinish}>
|
||||
<Form.Item name="db_type" label="DB Type" className="mb-3" rules={[{ required: true }]}>
|
||||
<Select aria-readonly={lockDBType} disabled={lockDBType} options={dbTypeList} />
|
||||
</Form.Item>
|
||||
<Form.Item name="db_name" label="DB Name" className="mb-3" rules={[{ required: true }]}>
|
||||
<Input readOnly={!!editValue} disabled={!!editValue} />
|
||||
</Form.Item>
|
||||
{form.getFieldValue('db_type') === 'omc' ? (
|
||||
<Form.Item name="db_name" label="DB Name" className="mb-3" rules={[{ required: true }]}>
|
||||
<Select
|
||||
optionRender={(option, { index }) => {
|
||||
const item = omcDBList[index] as any;
|
||||
return (
|
||||
<div key={option.value} className="flex flex-col">
|
||||
<span className="text-[18px]">{item?.dbName}</span>
|
||||
<span>
|
||||
<span>env: </span>
|
||||
<span className="text-gray-500">{item.env}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>account: </span>
|
||||
<span className="text-gray-500">{item.account}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>searchName: </span>
|
||||
<Tooltip title={item.searchName}>
|
||||
<span className="text-gray-500">{item.searchName}</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
notFoundContent={omcListLoading ? <Spin size="small" /> : null}
|
||||
showSearch
|
||||
options={omcDBList}
|
||||
onSearch={fetchOmcList}
|
||||
onSelect={(searchName) => {
|
||||
console.log(searchName, 'searchName');
|
||||
|
||||
const item = omcDBList?.find((item: any) => item.value === searchName) as any;
|
||||
form.setFieldsValue({
|
||||
db_arn: item?.arn,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item name="db_name" label="DB Name" className="mb-3" rules={[{ required: true }]}>
|
||||
<Input readOnly={!!editValue} disabled={!!editValue} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{fileDb === true && (
|
||||
<Form.Item name="db_path" label="Path" className="mb-3" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
{fileDb === false && (
|
||||
{fileDb === false && form.getFieldValue('db_type') !== 'omc' && (
|
||||
<>
|
||||
<Form.Item name="db_user" label="Username" className="mb-3" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
@@ -106,7 +191,11 @@ function FormDialog({ open, choiceDBType, dbTypeList, editValue, dbNames, onClos
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{form.getFieldValue('db_type') === 'omc' && (
|
||||
<Form.Item name="db_arn" label="Arn" className="mb-3" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item name="comment" label="Remark" className="mb-3">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
@@ -25,6 +25,8 @@ interface FlowCardProps {
|
||||
}
|
||||
|
||||
const FlowCard: React.FC<FlowCardProps> = ({ flow, onCopy, deleteCallback }) => {
|
||||
console.log(flow, 'flow');
|
||||
|
||||
const { model } = useContext(ChatContext);
|
||||
const { t } = useTranslation();
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
@@ -76,8 +78,8 @@ const FlowCard: React.FC<FlowCardProps> = ({ flow, onCopy, deleteCallback }) =>
|
||||
title={flow.name}
|
||||
desc={flow.description}
|
||||
tags={[
|
||||
{ text: flow.source, color: flow.source === 'DBGPT-WEB' ? 'green' : 'blue', border: true },
|
||||
{ text: flow.editable ? 'Editable' : 'Can not Edit', color: flow.editable ? 'green' : 'gray', border: true },
|
||||
{ text: flow.source, border: true, color: flow.source === 'DBGPT-WEB' ? 'green' : 'blue' },
|
||||
{ text: flow.editable ? 'Editable' : 'Can not Edit', color: flow.editable ? 'green' : 'gray' },
|
||||
{
|
||||
text: (
|
||||
<>
|
||||
@@ -120,7 +122,7 @@ const FlowCard: React.FC<FlowCardProps> = ({ flow, onCopy, deleteCallback }) =>
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div className="w-full h-40 shadow-[inset_0_0_16px_rgba(50,50,50,.05)]">
|
||||
<div className="w-full h-[150px] shadow-[inset_0_0_16px_rgba(50,50,50,.05)]">
|
||||
<FlowPreview flowData={flow.flow_data} />
|
||||
</div>
|
||||
</GptCard>
|
||||
|
@@ -1,12 +1,13 @@
|
||||
function SplitScreenHeight() {
|
||||
return (
|
||||
<svg width="1em" height="1em" fill="currentColor" viewBox="0 0 1024 1024" version="1.1">
|
||||
<path
|
||||
d="M161.05472 919.3472h701.9008a58.71616 58.71616 0 0 0 58.65472-58.65472V180.40832a58.71616 58.71616 0 0 0-58.65472-58.65472H161.09568a58.03008 58.03008 0 0 0-41.4208 17.08032A58.1632 58.1632 0 0 0 102.4 180.30592v680.38656a58.64448 58.64448 0 0 0 58.65472 58.65472z m385.15712-589.568V190.08512h306.95424v660.93056H546.21184V329.7792zM170.83392 190.08512h306.95424v660.93056H170.83392V190.08512z"
|
||||
p-id="13913"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg width="1em" height="1em" fill="currentColor" viewBox="0 0 1024 1024" version="1.1">
|
||||
<path
|
||||
d="M161.05472 919.3472h701.9008a58.71616 58.71616 0 0 0 58.65472-58.65472V180.40832a58.71616 58.71616 0 0 0-58.65472-58.65472H161.09568a58.03008 58.03008 0 0 0-41.4208 17.08032A58.1632 58.1632 0 0 0 102.4 180.30592v680.38656a58.64448 58.64448 0 0 0 58.65472 58.65472z m385.15712-589.568V190.08512h306.95424v660.93056H546.21184V329.7792zM170.83392 190.08512h306.95424v660.93056H170.83392V190.08512z"
|
||||
p-id="13913"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default SplitScreenHeight;
|
||||
|
@@ -10,3 +10,4 @@ function SplitScreenWeight() {
|
||||
}
|
||||
|
||||
export default SplitScreenWeight;
|
||||
|
@@ -43,7 +43,7 @@ export default function ArgumentsModal({ space, argumentsShow, setArgumentsShow
|
||||
label={t('recall_score')}
|
||||
name={['embedding', 'recall_score']}
|
||||
>
|
||||
<Input className="mb-5 h-12" placeholder={t('Please_input_the_owner')} />
|
||||
<Input className="mb-5 h-12" placeholder="请输入" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import { FileTextFilled, FileWordTwoTone, IeCircleFilled } from '@ant-design/icons';
|
||||
import { FileTextFilled, FileWordTwoTone, IeCircleFilled, YuqueFilled } from '@ant-design/icons';
|
||||
|
||||
export default function DocIcon({ type }: { type: string }) {
|
||||
if (type === 'TEXT') {
|
||||
return <FileTextFilled className="text-[#2AA3FF] mr-2 !text-lg" />;
|
||||
} else if (type === 'DOCUMENT') {
|
||||
return <FileWordTwoTone className="text-[#2AA3FF] mr-2 !text-lg" />;
|
||||
} else if (type === 'YUQUEURL') {
|
||||
return <YuqueFilled className="text-[#2AA3FF] mr-2 !text-lg" />;
|
||||
} else {
|
||||
return <IeCircleFilled className="text-[#2AA3FF] mr-2 !text-lg" />;
|
||||
}
|
||||
|
@@ -1,37 +1,101 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Button, Card, Space, Divider, Empty, Spin, Tag, Tooltip, Modal } from 'antd';
|
||||
import { DeleteFilled, InteractionFilled, PlusOutlined, ToolFilled, EyeFilled, WarningOutlined, DeploymentUnitOutlined} from '@ant-design/icons';
|
||||
import { apiInterceptors, delDocument, getDocumentList, syncDocument } from '@/client/api';
|
||||
import {
|
||||
apiInterceptors,
|
||||
delDocument,
|
||||
editChunk,
|
||||
getDocumentList,
|
||||
// getKnowledgeAdmins,
|
||||
searchDocumentList,
|
||||
syncDocument,
|
||||
updateKnowledgeAdmins,
|
||||
} from '@/client/api';
|
||||
import { IDocument, ISpace } from '@/types/knowledge';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
EllipsisOutlined,
|
||||
ExperimentOutlined,
|
||||
EyeOutlined,
|
||||
LoadingOutlined,
|
||||
MinusCircleOutlined,
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
SyncOutlined,
|
||||
ToolFilled,
|
||||
WarningOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Button, Card, Divider, Dropdown, Empty, Form, Input, Modal, Select, Space, Spin, Tag, Tooltip, message, notification } from 'antd';
|
||||
import cls from 'classnames';
|
||||
import moment from 'moment';
|
||||
import ArgumentsModal from './arguments-modal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ArgumentsModal from './arguments-modal';
|
||||
import DocIcon from './doc-icon';
|
||||
import RecallTestModal from './RecallTestModal';
|
||||
|
||||
interface IProps {
|
||||
space: ISpace;
|
||||
addStatus?: string;
|
||||
onAddDoc: (spaceName: string) => void;
|
||||
onDeleteDoc: () => void;
|
||||
}
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
const SyncContent: React.FC<{ name: string; id: number }> = ({ name, id }) => {
|
||||
const [syncLoading, setSyncLoading] = useState<boolean>(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSync = async (spaceName: string, id: number) => {
|
||||
setSyncLoading(true);
|
||||
const res = await apiInterceptors(syncDocument(spaceName, { doc_ids: [id] }));
|
||||
setSyncLoading(false);
|
||||
if (res[2]?.success) {
|
||||
message.success(t('Synchronization_initiated'));
|
||||
}
|
||||
};
|
||||
|
||||
if (syncLoading) {
|
||||
return <Spin indicator={<LoadingOutlined spin />} />;
|
||||
}
|
||||
return (
|
||||
<Space
|
||||
onClick={() => {
|
||||
handleSync(name, id);
|
||||
}}
|
||||
>
|
||||
<SyncOutlined />
|
||||
<span>{t('Sync')}</span>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default function DocPanel(props: IProps) {
|
||||
const { space } = props;
|
||||
const [form] = Form.useForm();
|
||||
const { space, addStatus } = props;
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const page_size = 18;
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [admins, setAdmins] = useState<string[]>([]);
|
||||
const [documents, setDocuments] = useState<any>([]);
|
||||
const [searchDocuments, setSearchDocuments] = useState<any>([]);
|
||||
const [argumentsShow, setArgumentsShow] = useState<boolean>(false);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
|
||||
const [editOpen, setEditOpen] = useState<boolean>(false);
|
||||
const [curDoc, setCurDoc] = useState<IDocument>();
|
||||
|
||||
// 召回测试弹窗
|
||||
const [recallTestOpen, setRecallTestOpen] = useState<boolean>(false);
|
||||
|
||||
const currentPageRef = useRef(1);
|
||||
|
||||
const hasMore = useMemo(() => {
|
||||
return documents.length < total;
|
||||
}, [documents.length, total]);
|
||||
return documents?.length < total;
|
||||
}, [documents, total]);
|
||||
|
||||
const showDeleteConfirm = (row: any) => {
|
||||
confirm({
|
||||
@@ -47,24 +111,33 @@ export default function DocPanel(props: IProps) {
|
||||
});
|
||||
};
|
||||
|
||||
async function fetchDocuments() {
|
||||
setIsLoading(true);
|
||||
const [_, data] = await apiInterceptors(
|
||||
getDocumentList(space.name, {
|
||||
page: currentPageRef.current,
|
||||
page_size,
|
||||
}),
|
||||
);
|
||||
setDocuments(data?.data);
|
||||
setTotal(data?.total || 0);
|
||||
setIsLoading(false);
|
||||
}
|
||||
const {
|
||||
run: fetchDocuments,
|
||||
refresh,
|
||||
loading: isLoading,
|
||||
} = useRequest(
|
||||
async () =>
|
||||
await apiInterceptors(
|
||||
getDocumentList(space.name, {
|
||||
page: currentPageRef.current,
|
||||
page_size,
|
||||
}),
|
||||
),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (res) => {
|
||||
const [, data] = res;
|
||||
setDocuments(data?.data);
|
||||
setSearchDocuments(data?.data);
|
||||
setTotal(data?.total || 0);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const loadMoreDocuments = async () => {
|
||||
if (!hasMore) {
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
currentPageRef.current += 1;
|
||||
const [_, data] = await apiInterceptors(
|
||||
getDocumentList(space.name, {
|
||||
@@ -73,11 +146,7 @@ export default function DocPanel(props: IProps) {
|
||||
}),
|
||||
);
|
||||
setDocuments([...documents, ...data!.data]);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleSync = async (spaceName: string, id: number) => {
|
||||
await apiInterceptors(syncDocument(spaceName, { doc_ids: [id] }));
|
||||
setSearchDocuments([...documents, ...data!.data]);
|
||||
};
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
@@ -93,10 +162,10 @@ export default function DocPanel(props: IProps) {
|
||||
const handleArguments = () => {
|
||||
setArgumentsShow(true);
|
||||
};
|
||||
|
||||
const openGraphVisualPage = () => {
|
||||
router.push(`/knowledge/graph/?spaceName=${space.name}`);
|
||||
}
|
||||
|
||||
const renderResultTag = (status: string, result: string) => {
|
||||
let color;
|
||||
switch (status) {
|
||||
@@ -122,90 +191,258 @@ export default function DocPanel(props: IProps) {
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
// const getAdmins = useCallback(async () => {
|
||||
// const [err, data] = await apiInterceptors(getKnowledgeAdmins(space.id as string));
|
||||
|
||||
// if (!data || !data.length) return;
|
||||
// setAdmins(data as string[]);
|
||||
// }, [space.id]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDocuments();
|
||||
}, [space]);
|
||||
// getAdmins();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (addStatus === 'finish') {
|
||||
fetchDocuments();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [addStatus]);
|
||||
|
||||
const updateAdmins = useCallback(
|
||||
async (options: string[]) => {
|
||||
const { data } = await updateKnowledgeAdmins({
|
||||
space_id: space.id as string,
|
||||
user_nos: options as any,
|
||||
});
|
||||
if (!data.success) {
|
||||
// getAdmins();
|
||||
notification.error({ description: data.err_msg, message: 'Update Error' });
|
||||
} else {
|
||||
message.success(t('Edit_Success'));
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[space.id],
|
||||
);
|
||||
const handleChange = (value: string[]) => {
|
||||
updateAdmins(value);
|
||||
setAdmins(value);
|
||||
};
|
||||
|
||||
const { run: search, loading: searchLoading } = useRequest(
|
||||
async (id, doc_name: string) => {
|
||||
const [, res] = await apiInterceptors(searchDocumentList(space.name, { doc_name }));
|
||||
return res;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
debounceWait: 500,
|
||||
onSuccess: (data) => {
|
||||
console.log(data);
|
||||
setSearchDocuments(data?.data);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { run: editChunkRun, loading: chunkLoading } = useRequest(
|
||||
async (values: any) => {
|
||||
return await editChunk(props.space.name, {
|
||||
questions: values.questions?.map((item: any) => item.question),
|
||||
doc_id: curDoc?.id || '',
|
||||
doc_name: values.doc_name,
|
||||
});
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: async (res) => {
|
||||
if (res.data.success) {
|
||||
message.success(t('Edit_Success'));
|
||||
await fetchDocuments();
|
||||
setEditOpen(false);
|
||||
} else {
|
||||
message.error(res.data.err_msg);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const renderDocumentCard = () => {
|
||||
if (documents?.length > 0) {
|
||||
return (
|
||||
<div className="max-h-96 overflow-auto max-w-3/4">
|
||||
<div className="mt-3 grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2 lg:grid-cols-3 xl:gap-x-5">
|
||||
{documents.map((document: IDocument) => {
|
||||
return (
|
||||
<Card
|
||||
key={document.id}
|
||||
className=" dark:bg-[#484848] relative shrink-0 grow-0 cursor-pointer rounded-[10px] border border-gray-200 border-solid w-full"
|
||||
title={
|
||||
<Tooltip title={document.doc_name}>
|
||||
<div className="truncate ">
|
||||
<DocIcon type={document.doc_type} />
|
||||
<span>{document.doc_name}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
extra={
|
||||
<div className="mx-3">
|
||||
<Tooltip title={'detail'}>
|
||||
<EyeFilled
|
||||
className="mr-2 !text-lg"
|
||||
style={{ color: '#1b7eff', fontSize: '20px' }}
|
||||
onClick={() => {
|
||||
router.push(`/knowledge/chunk/?spaceName=${space.name}&id=${document.id}`);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={'Sync'}>
|
||||
<InteractionFilled
|
||||
className="mr-2 !text-lg"
|
||||
style={{ color: '#1b7eff', fontSize: '20px' }}
|
||||
onClick={() => {
|
||||
handleSync(space.name, document.id);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={'Delete'}>
|
||||
<DeleteFilled
|
||||
className="text-[#ff1b2e] !text-lg"
|
||||
onClick={() => {
|
||||
showDeleteConfirm(document);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p className="mt-2 font-semibold ">{t('Size')}:</p>
|
||||
<p>{document.chunk_size} chunks</p>
|
||||
<p className="mt-2 font-semibold ">{t('Last_Sync')}:</p>
|
||||
<p>{moment(document.last_sync).format('YYYY-MM-DD HH:MM:SS')}</p>
|
||||
<p className="mt-2 mb-2">{renderResultTag(document.status, document.result)}</p>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<div className="mb-4">
|
||||
{/* <div className="mb-1">管理员(工号,去前缀0):</div> */}
|
||||
<div className="flex w-full justify-end">
|
||||
{/* <Select
|
||||
mode="tags"
|
||||
value={admins}
|
||||
style={{ width: '50%' }}
|
||||
onChange={handleChange}
|
||||
tokenSeparators={[',']}
|
||||
options={admins.map((item: string) => ({ label: item, value: item }))}
|
||||
/> */}
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
await refresh();
|
||||
}}
|
||||
loading={isLoading}
|
||||
>
|
||||
{t('Refresh_status')}
|
||||
</Button>
|
||||
</div>
|
||||
{hasMore && (
|
||||
<Divider>
|
||||
<span className="cursor-pointer" onClick={loadMoreDocuments}>
|
||||
{t('Load_more')}
|
||||
</span>
|
||||
</Divider>
|
||||
</div>
|
||||
<div className="flex flex-col h-full p-3 border rounded-md">
|
||||
{documents?.length > 0 ? (
|
||||
<>
|
||||
<div className="flex flex-1 justify-between items-center">
|
||||
<Input
|
||||
className="w-1/3"
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder={t('please_enter_the_keywords')}
|
||||
onChange={async (e) => {
|
||||
await search(space.id, e.target.value);
|
||||
}}
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await apiInterceptors(syncDocument(space.name, { doc_ids: [] }));
|
||||
message.success(t('Synchronization_initiated'));
|
||||
}}
|
||||
className="border"
|
||||
>
|
||||
{t('synchronization')}
|
||||
</Button>
|
||||
</div>
|
||||
<Spin spinning={searchLoading}>
|
||||
<>
|
||||
{searchDocuments.length > 0 ? (
|
||||
<div className="h-96 mt-3 grid grid-cols-3 gap-x-6 gap-y-5 overflow-y-auto">
|
||||
{searchDocuments.map((document: IDocument) => {
|
||||
return (
|
||||
<Card
|
||||
key={document.id}
|
||||
className=" dark:bg-[#484848] relative shrink-0 grow-0 cursor-pointer rounded-[10px] border border-gray-200 border-solid w-full max-h-64"
|
||||
title={
|
||||
<Tooltip title={document.doc_name}>
|
||||
<div className="truncate ">
|
||||
<DocIcon type={document.doc_type} />
|
||||
<span>{document.doc_name}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
extra={
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'publish',
|
||||
label: (
|
||||
<Space
|
||||
onClick={() => {
|
||||
router.push(`/construct/knowledge/chunk/?spaceName=${space.name}&id=${document.id}`);
|
||||
}}
|
||||
>
|
||||
<EyeOutlined />
|
||||
<span>{t('detail')}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: `${t('Sync')}`,
|
||||
label: <SyncContent name={space.name} id={document.id} />,
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
label: (
|
||||
<Space
|
||||
onClick={() => {
|
||||
setEditOpen(true);
|
||||
setCurDoc(document);
|
||||
}}
|
||||
>
|
||||
<EditOutlined />
|
||||
<span>{t('Edit')}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'del',
|
||||
label: (
|
||||
<Space
|
||||
onClick={() => {
|
||||
showDeleteConfirm(document);
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
<span>{t('Delete')}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
getPopupContainer={(node) => node.parentNode as HTMLElement}
|
||||
placement="bottomRight"
|
||||
autoAdjustOverflow={false}
|
||||
className="bg-gray-100 rounded-md"
|
||||
>
|
||||
<EllipsisOutlined className="p-2" />
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
<p className="mt-2 font-semibold ">{t('Size')}:</p>
|
||||
<p>{document.chunk_size} chunks</p>
|
||||
<p className="mt-2 font-semibold ">{t('Last_Sync')}:</p>
|
||||
<p>{moment(document.last_sync).format('YYYY-MM-DD HH:MM:SS')}</p>
|
||||
<p className="mt-2 mb-2">{renderResultTag(document.status, document.result)}</p>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<Empty className="flex flex-1 w-full py-10 flex-col items-center justify-center" image={Empty.PRESENTED_IMAGE_DEFAULT} />
|
||||
)}
|
||||
</>
|
||||
{hasMore && (
|
||||
<Divider>
|
||||
<span className="cursor-pointer" onClick={loadMoreDocuments}>
|
||||
{t('Load_more')}
|
||||
</span>
|
||||
</Divider>
|
||||
)}
|
||||
</Spin>
|
||||
</>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_DEFAULT}>
|
||||
<Button type="primary" className="flex items-center mx-auto" icon={<PlusOutlined />} onClick={handleAddDocument}>
|
||||
Create Now
|
||||
</Button>
|
||||
</Empty>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_DEFAULT}>
|
||||
<Button type="primary" className="flex items-center mx-auto" icon={<PlusOutlined />} onClick={handleAddDocument}>
|
||||
Create Now
|
||||
</Button>
|
||||
</Empty>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!curDoc) {
|
||||
return;
|
||||
}
|
||||
form.setFieldsValue({
|
||||
doc_name: curDoc.doc_name,
|
||||
questions: curDoc.questions?.map((ques) => {
|
||||
return {
|
||||
question: ques,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}, [curDoc, form]);
|
||||
|
||||
return (
|
||||
<div className="collapse-container pt-2 px-4">
|
||||
<div className="px-4">
|
||||
<Space>
|
||||
<Button size="middle" type="primary" className="flex items-center" icon={<PlusOutlined />} onClick={handleAddDocument}>
|
||||
{t('Add_Datasource')}
|
||||
@@ -216,10 +453,88 @@ export default function DocPanel(props: IProps) {
|
||||
{
|
||||
space.vector_type === 'KnowledgeGraph' && (<Button size="middle" className="flex items-center mx-2" icon={<DeploymentUnitOutlined />} onClick={openGraphVisualPage}>{t('View_Graph')}</Button>)
|
||||
}
|
||||
<Button icon={<ExperimentOutlined />} onClick={() => setRecallTestOpen(true)}>
|
||||
{t('Recall_test')}
|
||||
</Button>
|
||||
</Space>
|
||||
<Divider />
|
||||
<Spin spinning={isLoading}>{renderDocumentCard()}</Spin>
|
||||
<ArgumentsModal space={space} argumentsShow={argumentsShow} setArgumentsShow={setArgumentsShow} />
|
||||
{/* 编辑弹窗 */}
|
||||
<Modal
|
||||
title={t('Edit_document')}
|
||||
open={editOpen}
|
||||
onCancel={() => setEditOpen(false)}
|
||||
destroyOnClose={true}
|
||||
footer={[
|
||||
<Button key="back" onClick={() => setEditOpen(false)}>
|
||||
{t('cancel')}
|
||||
</Button>,
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
loading={chunkLoading}
|
||||
onClick={async () => {
|
||||
const values = form.getFieldsValue();
|
||||
await editChunkRun(values);
|
||||
}}
|
||||
>
|
||||
{t('verify')}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
initialValues={{
|
||||
doc_name: curDoc?.doc_name,
|
||||
questions: curDoc?.questions?.map((ques) => {
|
||||
return {
|
||||
question: ques,
|
||||
};
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Form.Item label={t('Document_name')} name="doc_name">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('Correlation_problem')}>
|
||||
<Form.List name="questions">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name }, index) => (
|
||||
<div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
|
||||
<Form.Item label="" name={[name, 'question']} className="grow">
|
||||
<Input placeholder="请输入" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<MinusCircleOutlined
|
||||
onClick={() => {
|
||||
remove(name);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add({ question: '', valid: false });
|
||||
}}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('Add_problem')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
{/* 召回测试弹窗 */}
|
||||
<RecallTestModal open={recallTestOpen} setOpen={setRecallTestOpen} space={space} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -29,6 +29,12 @@ export default function DocTypeForm(props: IProps) {
|
||||
subTitle: t('Upload_a_document'),
|
||||
iconType: 'DOCUMENT',
|
||||
},
|
||||
{
|
||||
type: 'YUQUEURL',
|
||||
title: t('yuque'),
|
||||
subTitle: t('Get_yuque_document'),
|
||||
iconType: 'YUQUEURL',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { Button, Form, Input, Upload, Spin, message } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { InboxOutlined } from '@ant-design/icons';
|
||||
import { apiInterceptors, addDocument, uploadDocument } from '@/client/api';
|
||||
import { addDocument, apiInterceptors, uploadDocument, addYuque } from '@/client/api';
|
||||
import { StepChangeParams } from '@/types/knowledge';
|
||||
import { InboxOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Input, Spin, Upload, message } from 'antd';
|
||||
import { RcFile, UploadChangeParam } from 'antd/es/upload';
|
||||
import { File, StepChangeParams } from '@/types/knowledge';
|
||||
import { UploadRequestOption as RcCustomRequestOptions } from 'rc-upload/lib/interface';
|
||||
import classNames from 'classnames';
|
||||
import { default as classNames, default as cls } from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
type FileParams = {
|
||||
file: RcFile;
|
||||
@@ -26,6 +26,8 @@ type FieldType = {
|
||||
originFileObj: FileParams;
|
||||
text: string;
|
||||
webPageUrl: string;
|
||||
questions: Record<string, any>[];
|
||||
doc_token?: string;
|
||||
};
|
||||
|
||||
const { Dragger } = Upload;
|
||||
@@ -36,11 +38,11 @@ export default function DocUploadForm(props: IProps) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [spinning, setSpinning] = useState<boolean>(false);
|
||||
const [files, setFiles] = useState<Array<File>>([]);
|
||||
const [files, setFiles] = useState<any>([]);
|
||||
|
||||
const upload = async (data: FieldType) => {
|
||||
const { docName, textSource, text, webPageUrl } = data;
|
||||
let docId;
|
||||
const { docName, textSource, text, webPageUrl, doc_token, questions = [], originFileObj } = data;
|
||||
let docId: any;
|
||||
setSpinning(true);
|
||||
switch (docType) {
|
||||
case 'URL':
|
||||
@@ -49,6 +51,7 @@ export default function DocUploadForm(props: IProps) {
|
||||
doc_name: docName,
|
||||
content: webPageUrl,
|
||||
doc_type: 'URL',
|
||||
questions: questions?.map((item) => item.question),
|
||||
}),
|
||||
);
|
||||
break;
|
||||
@@ -59,9 +62,43 @@ export default function DocUploadForm(props: IProps) {
|
||||
source: textSource,
|
||||
content: text,
|
||||
doc_type: 'TEXT',
|
||||
questions: questions.map((item) => item.question),
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case 'YUQUEURL':
|
||||
[, docId] = await apiInterceptors(
|
||||
addYuque({
|
||||
doc_name: docName,
|
||||
space_name: spaceName,
|
||||
content: webPageUrl,
|
||||
doc_type: 'YUQUEURL',
|
||||
doc_token: doc_token || '',
|
||||
questions: questions?.map((item) => item.question),
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case 'DOCUMENT':
|
||||
const file = originFileObj as any;
|
||||
const formData = new FormData();
|
||||
const filename = file?.name;
|
||||
const ques = questions.map((item) => item.question);
|
||||
formData.append('doc_name', filename);
|
||||
formData.append('doc_file', file);
|
||||
formData.append('doc_type', 'DOCUMENT');
|
||||
formData.append('questions', JSON.stringify(ques));
|
||||
[, docId] = await apiInterceptors(uploadDocument(spaceName, formData));
|
||||
console.log(docId);
|
||||
if (Number.isInteger(docId)) {
|
||||
setFiles((files: any) => {
|
||||
files.push({
|
||||
name: filename,
|
||||
doc_id: docId || -1,
|
||||
});
|
||||
return files;
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
setSpinning(false);
|
||||
if (docType === 'DOCUMENT' && files.length < 1) {
|
||||
@@ -86,28 +123,8 @@ export default function DocUploadForm(props: IProps) {
|
||||
const handleFileChange = ({ file, fileList }: UploadChangeParam) => {
|
||||
if (fileList.length === 0) {
|
||||
form.setFieldValue('originFileObj', null);
|
||||
}
|
||||
};
|
||||
|
||||
const uploadFile = async (options: RcCustomRequestOptions) => {
|
||||
const { onSuccess, onError, file } = options;
|
||||
const formData = new FormData();
|
||||
const filename = file?.name;
|
||||
formData.append('doc_name', filename);
|
||||
formData.append('doc_file', file);
|
||||
formData.append('doc_type', 'DOCUMENT');
|
||||
const [, docId] = await apiInterceptors(uploadDocument(spaceName, formData));
|
||||
if (Number.isInteger(docId)) {
|
||||
onSuccess && onSuccess(docId || 0);
|
||||
setFiles((files) => {
|
||||
files.push({
|
||||
name: filename,
|
||||
doc_id: docId || -1,
|
||||
});
|
||||
return files;
|
||||
});
|
||||
} else {
|
||||
onError && onError({ name: '', message: '' });
|
||||
form.setFieldValue('originFileObj', file);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -127,6 +144,40 @@ export default function DocUploadForm(props: IProps) {
|
||||
<Form.Item<FieldType> label={`${t('Text')}:`} name="text" rules={[{ required: true, message: t('Please_input_the_description') }]}>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={`${t('Correlation_problem')}:`}>
|
||||
<Form.List name="questions">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name }, index) => (
|
||||
<div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
|
||||
<Form.Item label="" name={[name, 'question']} className="grow">
|
||||
<Input placeholder={t('input_question')} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<MinusCircleOutlined
|
||||
onClick={() => {
|
||||
remove(name);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('Add_problem')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -144,6 +195,101 @@ export default function DocUploadForm(props: IProps) {
|
||||
>
|
||||
<Input className="mb-5 h-12" placeholder={t('Please_input_the_Web_Page_URL')} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={`${t('Correlation_problem')}:`}>
|
||||
<Form.List name="questions">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name }, index) => (
|
||||
<div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
|
||||
<Form.Item label="" name={[name, 'question']} className="grow">
|
||||
<Input placeholder={t('input_question')} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<MinusCircleOutlined
|
||||
onClick={() => {
|
||||
remove(name);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('Add_problem')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderYuquePage = () => {
|
||||
return (
|
||||
<>
|
||||
<Form.Item<FieldType> label={`${t('Name')}:`} name="docName" rules={[{ required: true, message: t('Please_input_the_name') }]}>
|
||||
<Input className="mb-5 h-12" placeholder={t('Please_input_the_name')} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={t('document_url')} name="webPageUrl" rules={[{ required: true, message: t('input_document_url') }]}>
|
||||
<Input className="mb-5 h-12" placeholder={t('input_document_url')} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label={t('document_token')}
|
||||
name="doc_token"
|
||||
tooltip={
|
||||
<>
|
||||
{t('Get_token')}
|
||||
<Typography.Link href="https://yuque.antfin-inc.com/lark/openapi/dh8zp4" target="_blank">
|
||||
{t('Reference_link')}
|
||||
</Typography.Link>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Input className="mb-5 h-12" placeholder={t('input_document_token')} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={`${t('Correlation_problem')}:`}>
|
||||
<Form.List name="questions">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name }, index) => (
|
||||
<div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
|
||||
<Form.Item label="" name={[name, 'question']} className="grow">
|
||||
<Input placeholder={t('input_question')} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<MinusCircleOutlined
|
||||
onClick={() => {
|
||||
remove(name);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('Add_problem')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -154,20 +300,54 @@ export default function DocUploadForm(props: IProps) {
|
||||
<Form.Item<FieldType> name="originFileObj" rules={[{ required: true, message: t('Please_select_file') }]}>
|
||||
<Dragger
|
||||
multiple
|
||||
beforeUpload={() => false}
|
||||
onChange={handleFileChange}
|
||||
maxCount={10}
|
||||
accept=".pdf,.ppt,.pptx,.xls,.xlsx,.doc,.docx,.txt,.md"
|
||||
customRequest={uploadFile}
|
||||
maxCount={1}
|
||||
accept=".pdf,.ppt,.pptx,.xls,.xlsx,.doc,.docx,.txt,.md,.zip"
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p style={{ color: 'rgb(22, 108, 255)', fontSize: '20px' }}>{t('Select_or_Drop_file')}</p>
|
||||
<p className="ant-upload-hint" style={{ color: 'rgb(22, 108, 255)' }}>
|
||||
PDF, PowerPoint, Excel, Word, Text, Markdown,
|
||||
PDF, PowerPoint, Excel, Word, Text, Markdown, Zip1
|
||||
</p>
|
||||
</Dragger>
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label="关联问题:">
|
||||
<Form.List name="questions">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name }, index) => (
|
||||
<div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
|
||||
<Form.Item label="" name={[name, 'question']} className="grow">
|
||||
<Input placeholder="请输入问题" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<MinusCircleOutlined
|
||||
onClick={() => {
|
||||
remove(name);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('Add_problem')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -178,6 +358,8 @@ export default function DocUploadForm(props: IProps) {
|
||||
return renderWebPage();
|
||||
case 'DOCUMENT':
|
||||
return renderDocument();
|
||||
case 'YUQUEURL':
|
||||
return renderYuquePage();
|
||||
default:
|
||||
return renderText();
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ let intervalId: string | number | NodeJS.Timeout | undefined;
|
||||
|
||||
export default function Segmentation(props: IProps) {
|
||||
const { spaceName, docType, uploadFiles, handleStepChange } = props;
|
||||
console.log(docType, 'doctype');
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [files, setFiles] = useState(uploadFiles);
|
||||
@@ -29,6 +30,7 @@ export default function Segmentation(props: IProps) {
|
||||
const [strategies, setStrategies] = useState<Array<IChunkStrategyResponse>>([]);
|
||||
const [syncStatus, setSyncStatus] = useState<string>('');
|
||||
|
||||
const spaceId = localStorage.getItem('cur_space_id');
|
||||
async function getStrategies() {
|
||||
setLoading(true);
|
||||
const [, allStrategies] = await apiInterceptors(getChunkStrategies());
|
||||
@@ -41,6 +43,7 @@ export default function Segmentation(props: IProps) {
|
||||
return () => {
|
||||
intervalId && clearInterval(intervalId);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleFinish = async (data: FieldType) => {
|
||||
@@ -61,6 +64,11 @@ export default function Segmentation(props: IProps) {
|
||||
handleStepChange({
|
||||
label: 'finish',
|
||||
});
|
||||
} else if (status === 'FAILED') {
|
||||
clearInterval(intervalId);
|
||||
handleStepChange({
|
||||
label: 'finish',
|
||||
});
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
@@ -77,8 +85,8 @@ export default function Segmentation(props: IProps) {
|
||||
fileStrategies.map((item) => {
|
||||
const name = item?.chunk_parameters?.chunk_strategy;
|
||||
if (!name) {
|
||||
// set default strategy
|
||||
item.chunk_parameters = { chunk_strategy: 'Automatic' };
|
||||
message.error(`Please select chunk strategy for ${item.name}.`);
|
||||
checked = false;
|
||||
}
|
||||
const strategy = strategies.filter((item) => item.strategy === name)[0];
|
||||
const newParam: any = {
|
||||
@@ -98,7 +106,7 @@ export default function Segmentation(props: IProps) {
|
||||
|
||||
async function updateSyncStatus(docIds: Array<number>) {
|
||||
const [, docs] = await apiInterceptors(
|
||||
getDocumentList(spaceName, {
|
||||
getDocumentList(spaceName as any, {
|
||||
doc_ids: docIds,
|
||||
}),
|
||||
);
|
||||
@@ -129,8 +137,9 @@ export default function Segmentation(props: IProps) {
|
||||
switch (docType) {
|
||||
case 'TEXT':
|
||||
case 'URL':
|
||||
case 'YUQUEURL':
|
||||
return fields?.map((field) => (
|
||||
<StrategyForm strategies={strategies} docType={docType} fileName={files![field.name].name} field={field} />
|
||||
<StrategyForm key={field.key} strategies={strategies} docType={docType} fileName={files![field.name].name} field={field} />
|
||||
));
|
||||
case 'DOCUMENT':
|
||||
return (
|
||||
|
@@ -8,6 +8,7 @@ import moment from 'moment';
|
||||
import { apiInterceptors, delSpace, newDialogue } from '@/client/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import GptCard from '../common/gpt-card';
|
||||
import IconFont from '@/ant-components/common/Icon';
|
||||
|
||||
interface IProps {
|
||||
space: ISpace;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { addSpace, apiInterceptors } from '@/client/api';
|
||||
import { IStorage, StepChangeParams } from '@/types/knowledge';
|
||||
import { StepChangeParams, IStorage } from '@/types/knowledge';
|
||||
import { Button, Form, Input, Spin, Select } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type FieldType = {
|
||||
@@ -50,6 +50,7 @@ export default function SpaceForm(props: IProps) {
|
||||
);
|
||||
setSpinning(false);
|
||||
const is_financial = domain_type === 'FinancialReport';
|
||||
localStorage.setItem('cur_space_id', JSON.stringify(data));
|
||||
res?.success && handleStepChange({ label: 'forward', spaceName, pace: is_financial ? 2 : 1, docType: is_financial ? 'DOCUMENT' : '' });
|
||||
};
|
||||
|
||||
@@ -80,14 +81,11 @@ export default function SpaceForm(props: IProps) {
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input className="mb-5 h-12" placeholder={t('Please_input_the_name')} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={t('Owner')} name="owner" rules={[{ required: true, message: t('Please_input_the_owner') }]}>
|
||||
<Input className="mb-5 h-12" placeholder={t('Please_input_the_owner')} />
|
||||
<Input className="h-12" placeholder={t('Please_input_the_name')} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={t('Storage')} name="storage" rules={[{ required: true, message: t('Please_select_the_storage') }]}>
|
||||
<Select className="mb-5 h-12" placeholder={t('Please_select_the_storage')} onChange={handleStorageChange}>
|
||||
{spaceConfig?.map((item) => {
|
||||
{spaceConfig?.map((item: any) => {
|
||||
return <Select.Option value={item.name}>{item.desc}</Select.Option>;
|
||||
})}
|
||||
</Select>
|
||||
@@ -95,14 +93,14 @@ export default function SpaceForm(props: IProps) {
|
||||
<Form.Item<FieldType> label={t('Domain')} name="field" rules={[{ required: true, message: t('Please_select_the_domain_type') }]}>
|
||||
<Select className="mb-5 h-12" placeholder={t('Please_select_the_domain_type')}>
|
||||
{spaceConfig
|
||||
?.find((item) => item.name === storage)
|
||||
?.domain_types.map((item) => {
|
||||
?.find((item: any) => item.name === storage)
|
||||
?.domain_types.map((item: any) => {
|
||||
return <Select.Option value={item.name}>{item.desc}</Select.Option>;
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={t('Description')} name="description" rules={[{ required: true, message: t('Please_input_the_description') }]}>
|
||||
<Input className="mb-5 h-12" placeholder={t('Please_input_the_description')} />
|
||||
<Input className="h-12" placeholder={t('Please_input_the_description')} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
|
@@ -38,10 +38,10 @@ export default function StrategyForm({ strategies, docType, fileName, field }: I
|
||||
if (!selectedStrategy) {
|
||||
return null;
|
||||
}
|
||||
if (selectedStrategy === DEFAULT_STRATEGY.strategy) {
|
||||
if (selectedStrategy === DEFAULT_STRATEGY.name) {
|
||||
return <p className="my-4">{DEFAULT_STRATEGY.desc}</p>;
|
||||
}
|
||||
const parameters = ableStrategies?.filter((i) => i.strategy === selectedStrategy)[0].parameters;
|
||||
const parameters = ableStrategies?.filter((i) => i.strategy === selectedStrategy)[0]?.parameters;
|
||||
if (!parameters || !parameters.length) {
|
||||
return <Alert className="my-2" type="warning" message={t('No_parameter')} />;
|
||||
}
|
||||
|
@@ -1,31 +1,30 @@
|
||||
import UserBar from '@/ant-components/layout/UserBar';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { apiInterceptors, delDialogue } from '@/client/api';
|
||||
import { STORAGE_LANG_KEY, STORAGE_THEME_KEY } from '@/utils';
|
||||
import { DarkSvg, SunnySvg, ModelSvg } from '@/components/icons';
|
||||
import { DarkSvg, ModelSvg, SunnySvg } from '@/components/icons';
|
||||
import { IChatDialogueSchema } from '@/types/chat';
|
||||
import { STORAGE_LANG_KEY, STORAGE_THEME_KEY, STORAGE_USERINFO_KEY } from '@/utils/constants/index';
|
||||
import Icon, {
|
||||
AppstoreOutlined,
|
||||
BuildOutlined,
|
||||
ConsoleSqlOutlined,
|
||||
PartitionOutlined,
|
||||
DeleteOutlined,
|
||||
MessageOutlined,
|
||||
ForkOutlined,
|
||||
GlobalOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
PlusOutlined,
|
||||
ShareAltOutlined,
|
||||
MenuOutlined,
|
||||
SettingOutlined,
|
||||
BuildOutlined,
|
||||
ForkOutlined,
|
||||
AppstoreOutlined,
|
||||
MessageOutlined,
|
||||
PartitionOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { Modal, message, Tooltip, Dropdown } from 'antd';
|
||||
import { Modal, Popover, Tooltip, message } from 'antd';
|
||||
import { ItemType } from 'antd/es/menu/hooks/useItems';
|
||||
import cls from 'classnames';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/zh-cn';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { ReactNode, useCallback, useContext, useMemo, useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type SettingItem = {
|
||||
@@ -33,7 +32,11 @@ type SettingItem = {
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
noDropdownItem?: boolean;
|
||||
onClick: () => void;
|
||||
onClick?: () => void;
|
||||
items?: ItemType[];
|
||||
onSelect?: (p: { key: string }) => void;
|
||||
defaultSelectedKeys?: string[];
|
||||
placement?: 'top' | 'topLeft';
|
||||
};
|
||||
|
||||
type RouteItem = {
|
||||
@@ -41,10 +44,11 @@ type RouteItem = {
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
path: string;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
function menuItemStyle(active?: boolean) {
|
||||
return `flex items-center h-10 hover:bg-[#F1F5F9] dark:hover:bg-theme-dark text-base w-full transition-colors whitespace-nowrap px-4 ${
|
||||
return `flex items-center h-12 hover:bg-[#F1F5F9] dark:hover:bg-theme-dark text-base w-full transition-colors whitespace-nowrap px-4 ${
|
||||
active ? 'bg-[#F1F5F9] dark:bg-theme-dark' : ''
|
||||
}`;
|
||||
}
|
||||
@@ -56,13 +60,17 @@ function smallMenuItemStyle(active?: boolean) {
|
||||
}
|
||||
|
||||
function SideBar() {
|
||||
const { chatId, scene, isMenuExpand, dialogueList, queryDialogueList, refreshDialogList, setIsMenuExpand, setAgent, mode, setMode } =
|
||||
const { chatId, scene, isMenuExpand, dialogueList, queryDialogueList, refreshDialogList, setIsMenuExpand, setAgent, mode, setMode, adminList } =
|
||||
useContext(ChatContext);
|
||||
const { pathname, replace } = useRouter();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [logo, setLogo] = useState<string>('/logo_zh_latest.png');
|
||||
|
||||
const [logo, setLogo] = useState<string>('/LOGO_1.png');
|
||||
|
||||
const hasAdmin = useMemo(() => {
|
||||
const { user_id } = JSON.parse(localStorage.getItem(STORAGE_USERINFO_KEY) || '{}');
|
||||
return adminList.some((admin) => admin.user_id === user_id);
|
||||
}, [adminList]);
|
||||
|
||||
const routes = useMemo(() => {
|
||||
const items: RouteItem[] = [
|
||||
{
|
||||
@@ -109,37 +117,133 @@ function SideBar() {
|
||||
},
|
||||
];
|
||||
return items;
|
||||
}, [i18n.language]);
|
||||
}, [t]);
|
||||
|
||||
const handleToggleMenu = () => {
|
||||
const handleToggleMenu = useCallback(() => {
|
||||
setIsMenuExpand(!isMenuExpand);
|
||||
};
|
||||
}, [isMenuExpand, setIsMenuExpand]);
|
||||
|
||||
const handleToggleTheme = useCallback(() => {
|
||||
const theme = mode === 'light' ? 'dark' : 'light';
|
||||
setMode(theme);
|
||||
localStorage.setItem(STORAGE_THEME_KEY, theme);
|
||||
}, [mode]);
|
||||
}, [mode, setMode]);
|
||||
|
||||
const handleChangeLang = useCallback(() => {
|
||||
const language = i18n.language === 'en' ? 'zh' : 'en';
|
||||
i18n.changeLanguage(language);
|
||||
if (language === 'zh') moment.locale('zh-cn');
|
||||
if (language === 'en') moment.locale('en');
|
||||
localStorage.setItem(STORAGE_LANG_KEY, language);
|
||||
}, [i18n.language, i18n.changeLanguage]);
|
||||
|
||||
}, [i18n]);
|
||||
const settings = useMemo(() => {
|
||||
const items: SettingItem[] = [
|
||||
{
|
||||
key: 'theme',
|
||||
name: t('Theme'),
|
||||
icon: mode === 'dark' ? <Icon component={DarkSvg} /> : <Icon component={SunnySvg} />,
|
||||
items: [
|
||||
{
|
||||
key: 'light',
|
||||
label: (
|
||||
<div className="py-1 flex justify-between gap-8 ">
|
||||
<span className="flex gap-2 items-center">
|
||||
<Image src="/pictures/theme_light.png" alt="english" width={38} height={32}></Image>
|
||||
<span>Light</span>
|
||||
</span>
|
||||
<span
|
||||
className={cls({
|
||||
block: mode === 'light',
|
||||
hidden: mode !== 'light',
|
||||
})}
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'dark',
|
||||
label: (
|
||||
<div className="py-1 flex justify-between gap-8 ">
|
||||
<span className="flex gap-2 items-center">
|
||||
<Image src="/pictures/theme_dark.png" alt="english" width={38} height={32}></Image>
|
||||
<span>Dark</span>
|
||||
</span>
|
||||
<span
|
||||
className={cls({
|
||||
block: mode === 'dark',
|
||||
hidden: mode !== 'dark',
|
||||
})}
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
onClick: handleToggleTheme,
|
||||
onSelect: ({ key }: { key: string }) => {
|
||||
if (mode === key) return;
|
||||
setMode(key as 'light' | 'dark');
|
||||
localStorage.setItem(STORAGE_THEME_KEY, key);
|
||||
},
|
||||
defaultSelectedKeys: [mode],
|
||||
placement: 'topLeft',
|
||||
},
|
||||
{
|
||||
key: 'language',
|
||||
name: t('language'),
|
||||
icon: <GlobalOutlined />,
|
||||
items: [
|
||||
{
|
||||
key: 'en',
|
||||
label: (
|
||||
<div className="py-1 flex justify-between gap-8 ">
|
||||
<span className="flex gap-2">
|
||||
<Image src="/icons/english.png" alt="english" width={21} height={21}></Image>
|
||||
<span>English</span>
|
||||
</span>
|
||||
<span
|
||||
className={cls({
|
||||
block: i18n.language === 'en',
|
||||
hidden: i18n.language !== 'en',
|
||||
})}
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'zh',
|
||||
label: (
|
||||
<div className="py-1 flex justify-between gap-8 ">
|
||||
<span className="flex gap-2">
|
||||
<Image src="/icons/zh.png" alt="english" width={21} height={21}></Image>
|
||||
<span>简体中文</span>
|
||||
</span>
|
||||
<span
|
||||
className={cls({
|
||||
block: i18n.language === 'zh',
|
||||
hidden: i18n.language !== 'zh',
|
||||
})}
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
onSelect: ({ key }: { key: string }) => {
|
||||
if (i18n.language === key) return;
|
||||
i18n.changeLanguage(key);
|
||||
if (key === 'zh') moment.locale('zh-cn');
|
||||
if (key === 'en') moment.locale('en');
|
||||
localStorage.setItem(STORAGE_LANG_KEY, key);
|
||||
},
|
||||
onClick: handleChangeLang,
|
||||
defaultSelectedKeys: [i18n.language],
|
||||
},
|
||||
{
|
||||
key: 'fold',
|
||||
@@ -150,7 +254,75 @@ function SideBar() {
|
||||
},
|
||||
];
|
||||
return items;
|
||||
}, [mode, handleChangeLang, handleToggleMenu, handleChangeLang]);
|
||||
}, [t, mode, handleToggleTheme, i18n, handleChangeLang, isMenuExpand, handleToggleMenu, setMode]);
|
||||
|
||||
const functions = useMemo(() => {
|
||||
const items: RouteItem[] = [
|
||||
{
|
||||
key: 'chat',
|
||||
name: t('chat_online'),
|
||||
icon: (
|
||||
<Image
|
||||
key="image_chat"
|
||||
src={pathname === '/chat' ? '/pictures/chat_active.png' : '/pictures/chat.png'}
|
||||
alt="chat_image"
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
),
|
||||
path: '/chat',
|
||||
isActive: pathname.startsWith('/chat'),
|
||||
},
|
||||
{
|
||||
key: 'explore',
|
||||
name: t('explore'),
|
||||
isActive: pathname === '/',
|
||||
icon: (
|
||||
<Image
|
||||
key="image_explore"
|
||||
src={pathname === '/' ? '/pictures/explore_active.png' : '/pictures/explore.png'}
|
||||
alt="construct_image"
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
),
|
||||
path: '/',
|
||||
},
|
||||
{
|
||||
key: 'construct',
|
||||
name: t('construct'),
|
||||
isActive: pathname.startsWith('/construct'),
|
||||
icon: (
|
||||
<Image
|
||||
key="image_construct"
|
||||
src={pathname.startsWith('/construct') ? '/pictures/app_active.png' : '/pictures/app.png'}
|
||||
alt="construct_image"
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
),
|
||||
path: '/construct/app',
|
||||
},
|
||||
];
|
||||
if (hasAdmin) {
|
||||
items.push({
|
||||
key: 'evaluation',
|
||||
name: '场景评测',
|
||||
icon: (
|
||||
<Image
|
||||
key="image_construct"
|
||||
src={pathname.startsWith('/evaluation') ? '/pictures/app_active.png' : '/pictures/app.png'}
|
||||
alt="construct_image"
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
),
|
||||
path: '/evaluation',
|
||||
isActive: pathname === '/evaluation',
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}, [t, pathname, hasAdmin]);
|
||||
|
||||
const dropDownRoutes: ItemType[] = useMemo(() => {
|
||||
return routes.map<ItemType>((item) => ({
|
||||
@@ -178,6 +350,18 @@ function SideBar() {
|
||||
}));
|
||||
}, [settings]);
|
||||
|
||||
const dropDownFunctions: ItemType[] = useMemo(() => {
|
||||
return functions.map<ItemType>((item) => ({
|
||||
key: item.key,
|
||||
label: (
|
||||
<Link href={item.path} className="text-base">
|
||||
{item.icon}
|
||||
<span className="ml-2 text-sm">{item.name}</span>
|
||||
</Link>
|
||||
),
|
||||
}));
|
||||
}, [functions]);
|
||||
|
||||
const handleDelChat = useCallback(
|
||||
(dialogue: IChatDialogueSchema) => {
|
||||
Modal.confirm({
|
||||
@@ -204,7 +388,7 @@ function SideBar() {
|
||||
},
|
||||
});
|
||||
},
|
||||
[refreshDialogList],
|
||||
[chatId, refreshDialogList, replace, scene],
|
||||
);
|
||||
|
||||
const handleClickChatItem = (item: IChatDialogueSchema) => {
|
||||
@@ -218,56 +402,42 @@ function SideBar() {
|
||||
message[success ? 'success' : 'error'](success ? 'Copy success' : 'Copy failed');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
queryDialogueList();
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// queryDialogueList();
|
||||
// }, [queryDialogueList]);
|
||||
|
||||
useEffect(() => {
|
||||
setLogo(mode === 'dark' ? '/WHITE_LOGO.png' : '/LOGO_1.png');
|
||||
}, [mode]);
|
||||
const language = i18n.language;
|
||||
if (language === 'zh') moment.locale('zh-cn');
|
||||
if (language === 'en') moment.locale('en');
|
||||
}, [])
|
||||
|
||||
// useEffect(() => {
|
||||
// setLogo(mode === 'dark' ? '/WHITE_LOGO.png' : '/logo_zh_latest.png');
|
||||
// }, [mode]);
|
||||
|
||||
if (!isMenuExpand) {
|
||||
return (
|
||||
<div className="flex flex-col justify-between h-screen bg-white dark:bg-[#232734] animate-fade animate-duration-300">
|
||||
<Link href="/" className="px-2 py-3">
|
||||
<Image src="/LOGO_SMALL.png" alt="DB-GPT" width={63} height={46} className="w-[63px] h-[46px]" />
|
||||
</Link>
|
||||
<div
|
||||
className="flex flex-col justify-between pt-4 h-screen bg-bar dark:bg-[#232734] animate-fade animate-duration-300"
|
||||
// onMouseEnter={() => {
|
||||
// setIsMenuExpand(true);
|
||||
// }}
|
||||
>
|
||||
<div>
|
||||
<Link href="/" className="flex items-center justify-center my-4 mx-auto w-12 h-12 bg-theme-primary rounded-full text-white">
|
||||
<PlusOutlined className="text-lg" />
|
||||
<Link href="/" className="flex justify-center items-center pb-4">
|
||||
<Image src="/logo_s_latest.png" alt="DB-GPT" width={40} height={40} />
|
||||
</Link>
|
||||
</div>
|
||||
{/* Chat List */}
|
||||
<div className="flex-1 overflow-y-scroll py-4 space-y-2">
|
||||
{dialogueList?.map((item) => {
|
||||
const active = item.conv_uid === chatId && item.chat_mode === scene;
|
||||
|
||||
return (
|
||||
<Tooltip key={item.conv_uid} title={item.user_name || item.user_input} placement="right">
|
||||
<Link
|
||||
href={`/chat?scene=${item.chat_mode}&id=${item.conv_uid}`}
|
||||
className={smallMenuItemStyle(active)}
|
||||
onClick={() => {
|
||||
handleClickChatItem(item);
|
||||
}}
|
||||
>
|
||||
<MessageOutlined />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
{functions.map((i) => (
|
||||
<Link key={i.key} className="h-12 flex items-center" href={i.path}>
|
||||
{i?.icon}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<Dropdown menu={{ items: dropDownRoutes }} placement="topRight">
|
||||
<div className={smallMenuItemStyle()}>
|
||||
<MenuOutlined />
|
||||
</div>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items: dropDownSettings }} placement="topRight">
|
||||
<div className={smallMenuItemStyle()}>
|
||||
<SettingOutlined />
|
||||
</div>
|
||||
</Dropdown>
|
||||
<UserBar onlyAvatar />
|
||||
{settings
|
||||
.filter((item) => item.noDropdownItem)
|
||||
.map((item) => (
|
||||
@@ -283,72 +453,67 @@ function SideBar() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-white dark:bg-[#232734]">
|
||||
{/* LOGO */}
|
||||
<Link href="/" className="p-2">
|
||||
<Image src={logo} alt="DB-GPT" width={239} height={60} className="w-full h-full" />
|
||||
</Link>
|
||||
<Link href="/" className="flex items-center justify-center mb-4 mx-4 h-11 bg-theme-primary rounded text-white">
|
||||
<PlusOutlined className="mr-2" />
|
||||
<span>{t('new_chat')}</span>
|
||||
</Link>
|
||||
{/* Chat List */}
|
||||
<div className="flex-1 overflow-y-scroll">
|
||||
{dialogueList?.map((item) => {
|
||||
const active = item.conv_uid === chatId && item.chat_mode === scene;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.conv_uid}
|
||||
href={`/chat?scene=${item.chat_mode}&id=${item.conv_uid}`}
|
||||
className={`group/item ${menuItemStyle(active)}`}
|
||||
onClick={() => {
|
||||
handleClickChatItem(item);
|
||||
}}
|
||||
>
|
||||
<MessageOutlined className="text-base" />
|
||||
<div className="flex-1 line-clamp-1 mx-2 text-sm">{item.user_name || item.user_input}</div>
|
||||
<div
|
||||
className="group-hover/item:opacity-100 cursor-pointer opacity-0 mr-1"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
copyLink(item);
|
||||
}}
|
||||
<div
|
||||
className="flex flex-col justify-between h-screen px-4 pt-4 bg-bar dark:bg-[#232734] animate-fade animate-duration-300"
|
||||
// onMouseLeave={() => {
|
||||
// setIsMenuExpand(false);
|
||||
// }}
|
||||
>
|
||||
<div>
|
||||
{/* LOGO */}
|
||||
<Link href="/" className="flex items-center justify-center p-2 pb-4">
|
||||
<Image src={logo} alt="DB-GPT" width={180} height={40} />
|
||||
</Link>
|
||||
{/* functions */}
|
||||
<div className="flex flex-col gap-4">
|
||||
{functions.map((item) => {
|
||||
return (
|
||||
<Link
|
||||
href={item.path}
|
||||
className={cls('flex items-center w-full h-12 px-4 cursor-pointer hover:bg-[#F1F5F9] dark:hover:bg-theme-dark hover:rounded-xl', {
|
||||
'bg-white rounded-xl dark:bg-black': item.isActive,
|
||||
})}
|
||||
key={item.key}
|
||||
>
|
||||
<ShareAltOutlined />
|
||||
</div>
|
||||
<div
|
||||
className="group-hover/item:opacity-100 cursor-pointer opacity-0"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleDelChat(item);
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<div className="mr-3">{item.icon}</div>
|
||||
<span className="text-sm">{t(item.name as any)}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Settings */}
|
||||
<div className="pt-4">
|
||||
<div className="max-h-52 overflow-y-auto scrollbar-default">
|
||||
{routes.map((item) => (
|
||||
<Link key={item.key} href={item.path} className={`${menuItemStyle(pathname === item.path)} overflow-hidden`}>
|
||||
<>
|
||||
{item.icon}
|
||||
<span className="ml-3 text-sm">{item.name}</span>
|
||||
</>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-around py-4 mt-2">
|
||||
<span className={cls('flex items-center w-full h-12 px-4 bg-[#F1F5F9] dark:bg-theme-dark rounded-xl')}>
|
||||
<div className="mr-3 w-full">
|
||||
<UserBar />
|
||||
</div>
|
||||
</span>
|
||||
<div className="flex items-center justify-around py-4 mt-2 border-t border-dashed border-gray-200 dark:border-gray-700">
|
||||
{settings.map((item) => (
|
||||
<Tooltip key={item.key} title={item.name}>
|
||||
<div className="flex-1 flex items-center justify-center cursor-pointer text-xl" onClick={item.onClick}>
|
||||
{item.icon}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div key={item.key}>
|
||||
<Popover content={item.name}>
|
||||
<div className="flex-1 flex items-center justify-center cursor-pointer text-xl" onClick={item.onClick}>
|
||||
{item.icon}
|
||||
</div>
|
||||
</Popover>
|
||||
{/* {item.items ? (
|
||||
<Dropdown
|
||||
menu={{ items: item.items, selectable: true, onSelect: item.onSelect, defaultSelectedKeys: item.defaultSelectedKeys }}
|
||||
placement={item.placement || 'top'}
|
||||
arrow
|
||||
>
|
||||
<span onClick={item.onClick}>{item.icon}</span>
|
||||
</Dropdown>
|
||||
) : (
|
||||
<Popover content={item.name}>
|
||||
<div className="flex-1 flex items-center justify-center cursor-pointer text-xl" onClick={item.onClick}>
|
||||
{item.icon}
|
||||
</div>
|
||||
</Popover>
|
||||
)} */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -39,9 +39,9 @@ if (typeof window !== 'undefined' && typeof window?.fetch === 'function') {
|
||||
if (activeRequests === 0) {
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
activeRequests++;
|
||||
|
||||
|
||||
try {
|
||||
const response = await originalFetch(...args);
|
||||
return response;
|
||||
|
@@ -48,19 +48,19 @@ function ModelCard({ info }: Props) {
|
||||
info.model_type,
|
||||
]}
|
||||
icon={MODEL_ICON_MAP[info.model_name]?.icon || '/models/huggingface.svg'}
|
||||
operations={[
|
||||
{
|
||||
children: (
|
||||
<div>
|
||||
<PauseCircleOutlined className="mr-2" />
|
||||
<span className="text-sm">Stop Model</span>
|
||||
</div>
|
||||
),
|
||||
onClick: () => {
|
||||
stopTheModel(info);
|
||||
},
|
||||
},
|
||||
]}
|
||||
// operations={[
|
||||
// {
|
||||
// children: (
|
||||
// <div>
|
||||
// <PauseCircleOutlined className="mr-2" />
|
||||
// <span className="text-sm">Stop Model</span>
|
||||
// </div>
|
||||
// ),
|
||||
// onClick: () => {
|
||||
// stopTheModel(info);
|
||||
// },
|
||||
// },
|
||||
// ]}
|
||||
>
|
||||
<div className="flex flex-col gap-1 px-4 pb-4 text-xs">
|
||||
<div className="flex overflow-hidden">
|
||||
|
@@ -1,68 +0,0 @@
|
||||
import { extendTheme } from '@mui/joy/styles';
|
||||
import colors from '@mui/joy/colors';
|
||||
|
||||
export const joyTheme = extendTheme({
|
||||
colorSchemes: {
|
||||
light: {
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
...colors.grey,
|
||||
solidBg: '#e6f4ff',
|
||||
solidColor: '#1677ff',
|
||||
solidHoverBg: '#e6f4ff',
|
||||
},
|
||||
neutral: {
|
||||
plainColor: '#4d4d4d',
|
||||
plainHoverColor: '#131318',
|
||||
plainHoverBg: '#EBEBEF',
|
||||
plainActiveBg: '#D8D8DF',
|
||||
plainDisabledColor: '#B9B9C6',
|
||||
},
|
||||
background: {
|
||||
body: '#F7F7F7',
|
||||
surface: '#fff',
|
||||
},
|
||||
text: {
|
||||
primary: '#505050',
|
||||
},
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: {
|
||||
...colors.grey,
|
||||
softBg: '#353539',
|
||||
softHoverBg: '#35353978',
|
||||
softDisabledBg: '#353539',
|
||||
solidBg: '#51525beb',
|
||||
solidHoverBg: '#51525beb',
|
||||
},
|
||||
neutral: {
|
||||
plainColor: '#D8D8DF',
|
||||
plainHoverColor: '#F7F7F8',
|
||||
plainHoverBg: '#353539',
|
||||
plainActiveBg: '#434356',
|
||||
plainDisabledColor: '#434356',
|
||||
outlinedBorder: '#353539',
|
||||
outlinedHoverBorder: '#454651',
|
||||
},
|
||||
text: {
|
||||
primary: '#FDFDFC',
|
||||
},
|
||||
background: {
|
||||
body: '#151622',
|
||||
surface: '#51525beb',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
body: 'Josefin Sans, sans-serif',
|
||||
display: 'Josefin Sans, sans-serif',
|
||||
},
|
||||
zIndex: {
|
||||
modal: 1001,
|
||||
},
|
||||
});
|
2
web/global.d.ts
vendored
2
web/global.d.ts
vendored
@@ -13,4 +13,4 @@ declare namespace JSX {
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'cytoscape-euler';
|
||||
declare module 'cytoscape-euler';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user