mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-13 13:10:29 +00:00
feat(web): add new ui web
This commit is contained in:
@@ -400,7 +400,6 @@ async def chat_completions(
|
||||
span_type=SpanType.CHAT,
|
||||
metadata=model_to_dict(dialogue),
|
||||
):
|
||||
|
||||
chat: BaseChat = await get_chat_instance(dialogue)
|
||||
|
||||
if not chat.prompt_template.stream_out:
|
||||
|
@@ -309,7 +309,6 @@ def _parse_and_check_local_dag(
|
||||
filepath: str | None = None,
|
||||
data: Dict[str, Any] | None = None,
|
||||
) -> Tuple[BaseOperator, DAG, DAGMetadata, Any]:
|
||||
|
||||
dag, dag_metadata = _parse_local_dag(name, filepath)
|
||||
|
||||
return _check_local_dag(dag, dag_metadata, data)
|
||||
@@ -344,7 +343,6 @@ def _check_local_dag(
|
||||
|
||||
|
||||
def _parse_local_dag(name: str, filepath: str | None = None) -> Tuple[DAG, DAGMetadata]:
|
||||
|
||||
system_app = SystemApp()
|
||||
DAGVar.set_current_system_app(system_app)
|
||||
|
||||
|
@@ -280,7 +280,6 @@ class BaseOperator(DAGNode, ABC, Generic[OUT], metaclass=BaseOperatorMeta):
|
||||
if call_data != EMPTY_DATA:
|
||||
call_data = {"data": call_data}
|
||||
with root_tracer.start_span("dbgpt.awel.operator.call_stream"):
|
||||
|
||||
out_ctx = await self._runner.execute_workflow(
|
||||
self, call_data, streaming_call=True, exist_dag_ctx=dag_ctx
|
||||
)
|
||||
@@ -291,7 +290,6 @@ class BaseOperator(DAGNode, ABC, Generic[OUT], metaclass=BaseOperatorMeta):
|
||||
out_ctx.current_task_context.task_output.output_stream
|
||||
)
|
||||
else:
|
||||
|
||||
# No stream output, wrap the output in a stream
|
||||
async def _gen():
|
||||
yield task_output.output
|
||||
|
@@ -164,7 +164,6 @@ def initialize_controller(
|
||||
controller_params: Optional[ModelControllerParameters] = None,
|
||||
system_app: Optional[SystemApp] = None,
|
||||
):
|
||||
|
||||
global controller
|
||||
if remote_controller_addr:
|
||||
controller.backend = _RemoteModelController(remote_controller_addr)
|
||||
|
@@ -72,7 +72,6 @@ class ModelInstanceIdentifier(ResourceIdentifier):
|
||||
|
||||
@dataclass
|
||||
class ModelInstanceStorageItem(StorageItem):
|
||||
|
||||
model_name: str
|
||||
host: str
|
||||
port: int
|
||||
|
@@ -57,7 +57,6 @@ class APIMixin(ABC):
|
||||
time.sleep(self._health_check_interval_secs)
|
||||
|
||||
def __del__(self):
|
||||
|
||||
self._heartbeat_stop_event.set()
|
||||
|
||||
def _check_health(self, url: str) -> Tuple[bool, str]:
|
||||
|
@@ -20,7 +20,7 @@ function UserBar({ onlyAvatar = false }) {
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem(STORAGE_USERINFO_KEY);
|
||||
window.location.href = `${process.env.ANT_BUC_LOGOUT_URL}&goto=${encodeURIComponent(window.location.href)}`;
|
||||
window.location.href = `${process.env.LOGOUT_URL}&goto=${encodeURIComponent(window.location.href)}`;
|
||||
};
|
||||
|
||||
return (
|
||||
|
@@ -1,6 +1,8 @@
|
||||
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 { createContext, useEffect, useMemo, useState } from 'react';
|
||||
@@ -112,6 +114,13 @@ const ChatContextProvider = ({ children }: { children: React.ReactElement }) =>
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (getUserId()) {
|
||||
queryAdminListRun();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [queryAdminListRun, getUserId()]);
|
||||
|
||||
useEffect(() => {
|
||||
setMode(getDefaultTheme());
|
||||
try {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { getUserId } from '@/utils';
|
||||
import { GET, POST, DELETE } from '../index';
|
||||
import type {
|
||||
getDataSetsRequest,
|
||||
@@ -15,34 +16,51 @@ export const getTestAuth = () => {
|
||||
return GET(`/api/v1/evaluate/test_auth`);
|
||||
};
|
||||
|
||||
const userId = getUserId();
|
||||
|
||||
export const getDataSets = (data: getDataSetsRequest) => {
|
||||
return GET<getDataSetsRequest, Record<string, any>>(`/api/v1/evaluate/datasets`, data);
|
||||
return GET<getDataSetsRequest, Record<string, any>>(`/api/v1/evaluate/datasets`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const uploadDataSets = (data: uploadDataSetsRequest) => {
|
||||
return POST<uploadDataSetsRequest, Record<string, any>>(`/api/v1/evaluate/dataset/upload`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
};
|
||||
export const uploadDataSetsContent = (data: uploadDataSetsRequest) => {
|
||||
return POST<uploadDataSetsRequest, Record<string, any>>(`/api/v1/evaluate/dataset/upload/content`, data);
|
||||
return POST<uploadDataSetsRequest, Record<string, any>>(`/api/v1/evaluate/dataset/upload/content`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const uploadDataSetsFile = (data: FormData) => {
|
||||
return POST<FormData, Record<string, any>>(`/api/v1/evaluate/dataset/upload/file`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const delDataSet = (params: delDataSetRequest) => {
|
||||
return DELETE<delDataSetRequest, Record<string, any>>(`/api/v1/evaluate/dataset`, params);
|
||||
return DELETE<delDataSetRequest, Record<string, any>>(`/api/v1/evaluate/dataset`, params, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
//download dataSet
|
||||
export const downloadDataSet = (params: delDataSetRequest) => {
|
||||
return GET<delDataSetRequest, { data: BlobPart }>(`/api/v1/evaluate/dataset/download`, params, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
},
|
||||
responseType: 'blob',
|
||||
@@ -52,6 +70,7 @@ export const downloadDataSet = (params: delDataSetRequest) => {
|
||||
export const downloadEvaluation = (params: downloadEvaluationRequest) => {
|
||||
return GET<downloadEvaluationRequest, { data: BlobPart }>(`/api/v1/evaluate/evaluation/result/download`, params, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
},
|
||||
responseType: 'blob',
|
||||
@@ -59,29 +78,57 @@ export const downloadEvaluation = (params: downloadEvaluationRequest) => {
|
||||
};
|
||||
//delete evaluation
|
||||
export const delEvaluation = (params: delEvaluationRequest) => {
|
||||
return DELETE<delEvaluationRequest, Record<string, any>>(`/api/v1/evaluate/evaluation`, params);
|
||||
return DELETE<delEvaluationRequest, Record<string, any>>(`/api/v1/evaluate/evaluation`, params, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
//get evaluations
|
||||
export const getEvaluations = (data: getEvaluationsRequest) => {
|
||||
return GET<getEvaluationsRequest, Record<string, any>>(`/api/v1/evaluate/evaluations`, data);
|
||||
return GET<getEvaluationsRequest, Record<string, any>>(`/api/v1/evaluate/evaluations`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const getMetrics = (data: getMetricsRequest) => {
|
||||
return GET<getMetricsRequest, Record<string, any>>(`/api/v1/evaluate/metrics`, data);
|
||||
return GET<getMetricsRequest, Record<string, any>>(`/api/v1/evaluate/metrics`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const showEvaluation = (data: Partial<createEvaluationsRequest>) => {
|
||||
return GET<Partial<createEvaluationsRequest>, Record<string, any>[]>(`/api/v1/evaluate/evaluation/detail/show`, data);
|
||||
return GET<Partial<createEvaluationsRequest>, Record<string, any>[]>(`/api/v1/evaluate/evaluation/detail/show`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const getStorageTypes = () => {
|
||||
return GET<undefined, Record<string, any>>(`/api/v1/evaluate/storage/types`, undefined);
|
||||
return GET<undefined, Record<string, any>>(`/api/v1/evaluate/storage/types`, undefined, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
//create evaluations
|
||||
export const createEvaluations = (data: createEvaluationsRequest) => {
|
||||
return POST<createEvaluationsRequest, Record<string, any>>(`/api/v1/evaluate/start`, data);
|
||||
return POST<createEvaluationsRequest, Record<string, any>>(`/api/v1/evaluate/start`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
//update evaluations
|
||||
export const updateEvaluations = (data: updateDataSetRequest) => {
|
||||
return POST<updateDataSetRequest, Record<string, any>>(`/api/v1/evaluate/dataset/members/update`, data);
|
||||
return POST<updateDataSetRequest, Record<string, any>>(`/api/v1/evaluate/dataset/members/update`, data, {
|
||||
headers: {
|
||||
'user-id': userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// export const cancelFeedback = (data: CancelFeedbackAddParams) => {
|
||||
|
@@ -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> = {
|
||||
@@ -41,6 +43,7 @@ ins.interceptors.request.use((request) => {
|
||||
if (!request.timeout) {
|
||||
request.timeout = isLongTimeApi ? 60000 : 100000;
|
||||
}
|
||||
request.headers.set(HEADER_USER_ID_KEY, getUserId());
|
||||
return request;
|
||||
});
|
||||
|
||||
|
@@ -1,16 +1,12 @@
|
||||
import { AddYuqueProps } from '@/types/knowledge';
|
||||
import { POST } from '../index';
|
||||
import { AddYuqueProps, RecallTestChunk, RecallTestProps } from '@/types/knowledge';
|
||||
import { GET, POST } from '../index';
|
||||
import { SearchDocumentParams } from '@/types/knowledge';
|
||||
|
||||
/**
|
||||
* 知识库编辑搜索
|
||||
*/
|
||||
export const searchDocumentList = (
|
||||
id: string,
|
||||
data: {
|
||||
doc_name: string;
|
||||
},
|
||||
) => {
|
||||
return POST<{ doc_name: string }, { data: string[]; total: number; page: number }>(`/knowledge/${id}/document/list`, data);
|
||||
export const searchDocumentList = (id: string, data: SearchDocumentParams) => {
|
||||
return POST<SearchDocumentParams, { data: string[]; total: number; page: number }>(`/knowledge/${id}/document/list`, data);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -26,3 +22,32 @@ export const addYuque = (data: AddYuqueProps) => {
|
||||
export const editChunk = (knowledgeName: string, data: { questions: string[]; doc_id: string | number; doc_name: string }) => {
|
||||
return POST<{ questions: string[]; doc_id: string | number; doc_name: string }, null>(`/knowledge/${knowledgeName}/document/edit`, data);
|
||||
};
|
||||
/**
|
||||
* 召回测试推荐问题
|
||||
*/
|
||||
export const recallTestRecommendQuestion = (id: string) => {
|
||||
return GET<{ id: string }, string[]>(`/knowledge/${id}/recommend_questions`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 召回方法选项
|
||||
*/
|
||||
export const recallMethodOptions = (id: string) => {
|
||||
return GET<{ id: string }, string[]>(`/knowledge/${id}/recall_retrievers`);
|
||||
};
|
||||
/**
|
||||
* 召回测试
|
||||
*/
|
||||
export const recallTest = (data: RecallTestProps, id: string) => {
|
||||
return POST<RecallTestProps, RecallTestChunk[]>(`/knowledge/${id}/recall_test`, data);
|
||||
};
|
||||
|
||||
// chunk模糊搜索
|
||||
export const searchChunk = (data: { document_id: string; content: string }, name: string) => {
|
||||
return POST<{ document_id: string; content: string }, string[]>(`/knowledge/${name}/chunk/list`, data);
|
||||
};
|
||||
|
||||
// chunk添加问题
|
||||
export const chunkAddQuestion = (data: { chunk_id: string; questions: string[] }) => {
|
||||
return POST<{ chunk_id: string; questions: string[] }, string[]>(`/knowledge/questions/chunk/edit`, data);
|
||||
};
|
||||
|
@@ -155,8 +155,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: number, data: Record<string, number | Array<number>>) => {
|
||||
return POST<Record<string, number | Array<number>>, IDocumentResponse>(`/knowledge/${spaceName}/document/list`, data);
|
||||
|
41
web_new/components/MenuModal/index.tsx
Normal file
41
web_new/components/MenuModal/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Menu, Modal, ModalProps } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
type Props = {
|
||||
items: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
onClick?: () => void;
|
||||
children?: React.ReactNode;
|
||||
}>;
|
||||
modal: ModalProps;
|
||||
};
|
||||
function MenuModal({ items, modal }: Props) {
|
||||
const [currentMenuKey, setCurrentMenuKey] = useState('edit');
|
||||
return (
|
||||
<Modal {...modal}>
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="w-1/6">
|
||||
<Menu
|
||||
className="h-full"
|
||||
selectedKeys={[currentMenuKey]}
|
||||
mode="inline"
|
||||
onSelect={(info) => {
|
||||
setCurrentMenuKey(info.key);
|
||||
}}
|
||||
inlineCollapsed={false}
|
||||
items={items.map((item) => ({ key: item.key, label: item.label }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-5/6">
|
||||
{items.map((item) => {
|
||||
if (item.key === currentMenuKey) {
|
||||
return <React.Fragment key={item.key}>{item.children}</React.Fragment>;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default MenuModal;
|
@@ -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;
|
||||
|
189
web_new/components/knowledge/RecallTestModal.tsx
Normal file
189
web_new/components/knowledge/RecallTestModal.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
import MarkDownContext from '@/ant-components/common/MarkdownContext';
|
||||
import { apiInterceptors, recallMethodOptions, recallTest, recallTestRecommendQuestion } from '@/client/api';
|
||||
import { ISpace, RecallTestProps } from '@/types/knowledge';
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Button, Card, Col, Empty, Form, Input, InputNumber, Modal, Popover, Row, Select, Spin, Tag } from 'antd';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
type RecallTestModalProps = {
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
space: ISpace;
|
||||
};
|
||||
|
||||
const tagColors = ['magenta', 'orange', 'geekblue', 'purple', 'cyan', 'green'];
|
||||
|
||||
const RecallTestModal: React.FC<RecallTestModalProps> = ({ open, setOpen, space }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [extraForm] = Form.useForm();
|
||||
|
||||
// 获取推荐问题
|
||||
const { data: questions = [], run: questionsRun } = useRequest(
|
||||
async () => {
|
||||
const [, res] = await apiInterceptors(recallTestRecommendQuestion(space.id + ''));
|
||||
return res ?? [];
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
// 召回方法选项
|
||||
const { data: options = [], run: optionsRun } = useRequest(
|
||||
async () => {
|
||||
const [, res] = await apiInterceptors(recallMethodOptions(space.id + ''));
|
||||
return res ?? [];
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (data) => {
|
||||
extraForm.setFieldValue('recall_retrievers', data);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// questionsRun();
|
||||
optionsRun();
|
||||
}
|
||||
}, [open, optionsRun, questionsRun]);
|
||||
|
||||
// 召回测试
|
||||
const {
|
||||
run: recallTestRun,
|
||||
data: resultList = [],
|
||||
loading,
|
||||
} = useRequest(
|
||||
async (props: RecallTestProps) => {
|
||||
const [, res] = await apiInterceptors(recallTest({ ...props }, space.id + ''));
|
||||
return res ?? [];
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
const onTest = async () => {
|
||||
form.validateFields().then(async (values) => {
|
||||
const extraVal = extraForm.getFieldsValue();
|
||||
console.log(extraVal);
|
||||
await recallTestRun({ recall_top_k: 1, recall_retrievers: options, ...values, ...extraVal });
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title="召回测试" width={'60%'} open={open} footer={false} onCancel={() => setOpen(false)} centered destroyOnClose={true}>
|
||||
<Card
|
||||
title="召回配置"
|
||||
size="small"
|
||||
className="my-4"
|
||||
extra={
|
||||
<Popover
|
||||
placement="bottomRight"
|
||||
trigger="hover"
|
||||
title="向量检索设置"
|
||||
content={
|
||||
<Form
|
||||
form={extraForm}
|
||||
initialValues={{
|
||||
recall_top_k: 1,
|
||||
}}
|
||||
>
|
||||
<Form.Item label="Topk" tooltip="基于相似度得分的前 k 个向量" name="recall_top_k">
|
||||
<InputNumber placeholder="请输入" className="w-full" />
|
||||
</Form.Item>
|
||||
<Form.Item label="召回方法" name="recall_retrievers">
|
||||
<Select
|
||||
mode="multiple"
|
||||
options={options.map((item) => {
|
||||
return { label: item, value: item };
|
||||
})}
|
||||
className="w-full"
|
||||
allowClear
|
||||
disabled
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="score阈值" name="recall_score_threshold">
|
||||
<InputNumber placeholder="请输入" className="w-full" step={0.1} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
}
|
||||
>
|
||||
<SettingOutlined className="text-lg" />
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical" onFinish={onTest}>
|
||||
<Form.Item label="测试问题" required={true} name="question" rules={[{ required: true, message: '请输入测试问题' }]} className="m-0 p-0">
|
||||
<div className="flex w-full items-center gap-8">
|
||||
<Input placeholder="请输入测试问题" autoComplete="off" allowClear className="w-1/2" />
|
||||
<Button type="primary" htmlType="submit">
|
||||
测试
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* {questions?.length > 0 && (
|
||||
<Col span={16}>
|
||||
<Form.Item label="推荐问题" tooltip="点击选择,自动填入">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{questions.map((item, index) => (
|
||||
<Tag
|
||||
color={tagColors[index]}
|
||||
key={item}
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
form.setFieldValue('question', item);
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)} */}
|
||||
</Form>
|
||||
</Card>
|
||||
<Card title="召回结果" size="small">
|
||||
<Spin spinning={loading}>
|
||||
{resultList.length > 0 ? (
|
||||
<div
|
||||
className="flex flex-col overflow-y-auto"
|
||||
style={{
|
||||
height: '45vh',
|
||||
}}
|
||||
>
|
||||
{resultList.map((item) => (
|
||||
<Card
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<Tag color="blue"># {item.chunk_id}</Tag>
|
||||
{item.metadata.prop_field.title}
|
||||
</div>
|
||||
}
|
||||
extra={
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-semibold">score:</span>
|
||||
<span className="text-blue-500">{item.score}</span>
|
||||
</div>
|
||||
}
|
||||
key={item.chunk_id}
|
||||
size="small"
|
||||
className="mb-4 border-gray-500 shadow-md"
|
||||
>
|
||||
<MarkDownContext>{item.content}</MarkDownContext>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Empty />
|
||||
)}
|
||||
</Spin>
|
||||
</Card>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecallTestModal;
|
@@ -13,16 +13,17 @@ import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
EllipsisOutlined,
|
||||
ExperimentOutlined,
|
||||
EyeOutlined,
|
||||
LoadingOutlined,
|
||||
MinusCircleOutlined,
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
SyncOutlined,
|
||||
ToolFilled,
|
||||
WarningOutlined,
|
||||
MinusCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useFavicon, useRequest } from 'ahooks';
|
||||
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';
|
||||
@@ -31,6 +32,7 @@ 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;
|
||||
@@ -84,6 +86,9 @@ export default function DocPanel(props: IProps) {
|
||||
const [editOpen, setEditOpen] = useState<boolean>(false);
|
||||
const [curDoc, setCurDoc] = useState<IDocument>();
|
||||
|
||||
// 召回测试弹窗
|
||||
const [recallTestOpen, setRecallTestOpen] = useState<boolean>(false);
|
||||
|
||||
const currentPageRef = useRef(1);
|
||||
|
||||
const hasMore = useMemo(() => {
|
||||
@@ -440,6 +445,9 @@ export default function DocPanel(props: IProps) {
|
||||
<Button size="middle" className="flex items-center mx-2" icon={<ToolFilled />} onClick={handleArguments}>
|
||||
Arguments
|
||||
</Button>
|
||||
<Button icon={<ExperimentOutlined />} onClick={() => setRecallTestOpen(true)}>
|
||||
召回测试
|
||||
</Button>
|
||||
</Space>
|
||||
<Divider />
|
||||
<Spin spinning={isLoading}>{renderDocumentCard()}</Spin>
|
||||
@@ -517,6 +525,8 @@ export default function DocPanel(props: IProps) {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
{/* 召回测试弹窗 */}
|
||||
<RecallTestModal open={recallTestOpen} setOpen={setRecallTestOpen} space={space} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import i18n from '@/app/i18n';
|
||||
import { getUserId } from '@/utils';
|
||||
import { HEADER_USER_ID_KEY } from '@/utils/constants/index';
|
||||
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import { message } from 'antd';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
type Props = {
|
||||
queryAgentURL?: string;
|
||||
app_code?: string;
|
||||
};
|
||||
|
||||
type ChatParams = {
|
||||
@@ -18,7 +21,7 @@ type ChatParams = {
|
||||
onError?: (content: string, error?: Error) => void;
|
||||
};
|
||||
|
||||
const useChat = ({ queryAgentURL = '/api/v1/chat/completions' }: Props) => {
|
||||
const useChat = ({ queryAgentURL = '/api/v1/chat/completions', app_code = '' }: Props) => {
|
||||
const [ctrl, setCtrl] = useState<AbortController>({} as AbortController);
|
||||
const chat = useCallback(
|
||||
async ({ data, chatId, onMessage, onClose, onDone, onError, ctrl }: ChatParams) => {
|
||||
@@ -31,6 +34,7 @@ const useChat = ({ queryAgentURL = '/api/v1/chat/completions' }: Props) => {
|
||||
const params = {
|
||||
...data,
|
||||
conv_uid: chatId,
|
||||
app_code,
|
||||
};
|
||||
|
||||
if (!params.conv_uid) {
|
||||
@@ -43,6 +47,7 @@ const useChat = ({ queryAgentURL = '/api/v1/chat/completions' }: Props) => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
[HEADER_USER_ID_KEY]: getUserId() ?? '',
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
signal: ctrl.signal,
|
||||
|
@@ -10,13 +10,16 @@ const nextConfig = {
|
||||
API_BASE_URL: process.env.API_BASE_URL,
|
||||
GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
|
||||
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
||||
GET_USER_URL: process.env.GET_USER_URL,
|
||||
LOGIN_URL: process.env.LOGIN_URL,
|
||||
LOGOUT_URL: process.env.LOGOUT_URL,
|
||||
},
|
||||
trailingSlash: true,
|
||||
images: { unoptimized: true },
|
||||
skipTrailingSlashRedirect: true,
|
||||
};
|
||||
|
||||
const withTM = require('next-transpile-modules')(['@berryv/g2-react', '@antv/g2', 'react-syntax-highlighter']);
|
||||
const withTM = require('next-transpile-modules')(['@berryv/g2-react','@antv/g2','react-syntax-highlighter']);
|
||||
|
||||
module.exports = withTM({
|
||||
...nextConfig,
|
||||
|
@@ -1,19 +1,22 @@
|
||||
import FloatHelper from '@/ant-components/layout/FloatHelper';
|
||||
import { ChatContext, ChatContextProvider } from '@/app/chat-context';
|
||||
import { addUser, apiInterceptors } from '@/client/api';
|
||||
import SideBar from '@/components/layout/side-bar';
|
||||
import { STORAGE_LANG_KEY } from '@/utils/constants/index';
|
||||
import TopProgressBar from '@/components/layout/top-progress-bar';
|
||||
import { STORAGE_LANG_KEY, STORAGE_USERINFO_KEY, STORAGE_USERINFO_VALID_TIME_KEY } from '@/utils/constants/index';
|
||||
import { App, ConfigProvider, MappingAlgorithm, theme } from 'antd';
|
||||
import enUS from 'antd/locale/en_US';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import classNames from 'classnames';
|
||||
import type { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import FloatHelper from '@/ant-components/layout/FloatHelper';
|
||||
import '../app/i18n';
|
||||
import '../nprogress.css';
|
||||
import '../styles/globals.css';
|
||||
import Head from 'next/head';
|
||||
|
||||
const antdDarkTheme: MappingAlgorithm = (seedToken, mapToken) => {
|
||||
return {
|
||||
@@ -54,9 +57,42 @@ function CssWrapper({ children }: { children: React.ReactElement }) {
|
||||
function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
||||
const { isMenuExpand, mode } = useContext(ChatContext);
|
||||
const { i18n } = useTranslation();
|
||||
const [isLogin, setIsLogin] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
// 登录检测
|
||||
const handleAuth = async () => {
|
||||
setIsLogin(false);
|
||||
// 如果已有登录信息,直接展示首页
|
||||
if (localStorage.getItem(STORAGE_USERINFO_KEY)) {
|
||||
setIsLogin(true);
|
||||
return;
|
||||
}
|
||||
const get_user_url = process.env.GET_USER_URL || '';
|
||||
var user_not_login_url = process.env.LOGIN_URL;
|
||||
// MOCK User info
|
||||
var user = {
|
||||
user_channel: `sys`,
|
||||
user_no: `dbgpt`,
|
||||
nick_name: ` `,
|
||||
}
|
||||
if (user) {
|
||||
localStorage.setItem(STORAGE_USERINFO_KEY, JSON.stringify(user));
|
||||
localStorage.setItem(STORAGE_USERINFO_VALID_TIME_KEY, Date.now().toString());
|
||||
setIsLogin(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleAuth();
|
||||
}, []);
|
||||
|
||||
if (!isLogin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
if (router.pathname.includes('mobile')) {
|
||||
return <>{children}</>;
|
||||
|
@@ -68,7 +68,7 @@ export const ChatContentContext = createContext<ChatContentProps>({
|
||||
|
||||
const Chat: React.FC = () => {
|
||||
const { model, currentDialogInfo } = useContext(ChatContext);
|
||||
const { chat, ctrl } = useChat({});
|
||||
const { chat, ctrl } = useChat({ app_code: currentDialogInfo.app_code || '' });
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const chatId = searchParams?.get('id') ?? '';
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import { apiInterceptors, getChunkList } from '@/client/api';
|
||||
import DocIcon from '@/components/knowledge/doc-icon';
|
||||
import { DoubleRightOutlined } from '@ant-design/icons';
|
||||
import { Breadcrumb, Card, Empty, Pagination, Space, Spin } from 'antd';
|
||||
import MarkDownContext from '@/ant-components/common/MarkdownContext';
|
||||
import { apiInterceptors, getChunkList, chunkAddQuestion } from '@/client/api';
|
||||
import MenuModal from '@/components/MenuModal';
|
||||
import { MinusCircleOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { App, Breadcrumb, Button, Card, Empty, Form, Input, Pagination, Space, Spin, Tag } from 'antd';
|
||||
import cls from 'classnames';
|
||||
import { debounce } from 'lodash';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,7 +18,17 @@ function ChunkList() {
|
||||
const [chunkList, setChunkList] = useState<any>([]);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [isExpand, setIsExpand] = useState<boolean>(false);
|
||||
// const [isExpand, setIsExpand] = useState<boolean>(false);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [currentChunkInfo, setCurrentChunkInfo] = useState<any>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [pageSize, setPageSize] = useState<number>(10);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { message } = App.useApp();
|
||||
|
||||
const {
|
||||
query: { id, spaceName },
|
||||
} = useRouter();
|
||||
@@ -35,6 +48,7 @@ function ChunkList() {
|
||||
};
|
||||
|
||||
const loaderMore = async (page: number, page_size: number) => {
|
||||
setPageSize(page_size);
|
||||
setLoading(true);
|
||||
const [_, data] = await apiInterceptors(
|
||||
getChunkList(spaceName as string, {
|
||||
@@ -45,6 +59,7 @@ function ChunkList() {
|
||||
);
|
||||
setChunkList(data?.data || []);
|
||||
setLoading(false);
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -52,6 +67,35 @@ function ChunkList() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, spaceName]);
|
||||
|
||||
const onSearch = async (e: any) => {
|
||||
const content = e.target.value;
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
const [_, data] = await apiInterceptors(
|
||||
getChunkList(spaceName as string, {
|
||||
document_id: id as string,
|
||||
page: currentPage,
|
||||
page_size: pageSize,
|
||||
content,
|
||||
}),
|
||||
);
|
||||
setChunkList(data?.data || []);
|
||||
};
|
||||
|
||||
// 添加问题
|
||||
const { run: addQuestionRun, loading: addLoading } = useRequest(
|
||||
async (questions: string[]) => apiInterceptors(chunkAddQuestion({ chunk_id: currentChunkInfo.id, questions })),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: async () => {
|
||||
message.success('添加成功');
|
||||
setIsModalOpen(false);
|
||||
await fetchChunks();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full w-full px-6 pb-6">
|
||||
<Breadcrumb
|
||||
@@ -69,33 +113,50 @@ function ChunkList() {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
className="w-1/5 h-10 mb-4"
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder={t('please_enter_the_keywords')}
|
||||
onChange={debounce(onSearch, 300)}
|
||||
allowClear
|
||||
/>
|
||||
</div>
|
||||
{chunkList?.length > 0 ? (
|
||||
<div className="h-full grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 grid-flow-row auto-rows-max gap-x-6 gap-y-10 overflow-y-auto relative">
|
||||
<Spin className="flex flex-col items-center justify-center absolute bottom-0 top-0 left-0 right-0" spinning={loading} />
|
||||
{chunkList?.map((chunk: any) => {
|
||||
{chunkList?.map((chunk: any, index: number) => {
|
||||
return (
|
||||
<Card
|
||||
hoverable
|
||||
key={chunk.id}
|
||||
title={
|
||||
<Space>
|
||||
<DocIcon type={chunk.doc_type} />
|
||||
<span>{chunk.doc_name}</span>
|
||||
<Space className="flex justify-between">
|
||||
<Tag color="blue"># {index + (currentPage - 1) * DEDAULT_PAGE_SIZE}</Tag>
|
||||
{/* <DocIcon type={chunk.doc_type} /> */}
|
||||
<span className="text-sm">{chunk.doc_name}</span>
|
||||
</Space>
|
||||
}
|
||||
className={cls('h-96 rounded-xl overflow-hidden', {
|
||||
'h-auto': isExpand,
|
||||
// 'h-auto': isExpand,
|
||||
'h-auto': true,
|
||||
})}
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setCurrentChunkInfo(chunk);
|
||||
console.log(chunk);
|
||||
}}
|
||||
>
|
||||
<p className="font-semibold">{t('Content')}:</p>
|
||||
<p>{chunk?.content}</p>
|
||||
<p className="font-semibold">{t('Meta_Data')}: </p>
|
||||
<p>{chunk?.meta_info}</p>
|
||||
<Space
|
||||
{/* <Space
|
||||
className="absolute bottom-0 right-0 left-0 flex items-center justify-center cursor-pointer text-[#1890ff] bg-[rgba(255,255,255,0.8)] z-30"
|
||||
onClick={() => setIsExpand(!isExpand)}
|
||||
>
|
||||
<DoubleRightOutlined rotate={isExpand ? -90 : 90} /> {isExpand ? '收起' : '展开'}
|
||||
</Space>
|
||||
</Space> */}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
@@ -113,6 +174,105 @@ function ChunkList() {
|
||||
showTotal={(total) => `Total ${total} items`}
|
||||
onChange={loaderMore}
|
||||
/>
|
||||
<MenuModal
|
||||
modal={{
|
||||
title: '手动录入',
|
||||
width: '70%',
|
||||
open: isModalOpen,
|
||||
footer: false,
|
||||
onCancel: () => setIsModalOpen(false),
|
||||
afterOpenChange: (open) => {
|
||||
if (open) {
|
||||
form.setFieldValue(
|
||||
'questions',
|
||||
JSON.parse(currentChunkInfo?.questions || '[]')?.map((item: any) => ({ question: item })),
|
||||
);
|
||||
}
|
||||
},
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
key: 'edit',
|
||||
label: '数据内容',
|
||||
children: (
|
||||
<div className="flex gap-4">
|
||||
<Card size="small" title="主要内容" className="w-2/3 flex-wrap overflow-y-auto">
|
||||
<MarkDownContext>{currentChunkInfo?.content}</MarkDownContext>
|
||||
</Card>
|
||||
<Card size="small" title="辅助数据" className="w-1/3">
|
||||
<MarkDownContext>{currentChunkInfo?.meta_info}</MarkDownContext>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: '添加问题',
|
||||
children: (
|
||||
<Card
|
||||
size="small"
|
||||
extra={
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
const formVal = form.getFieldsValue();
|
||||
if (!formVal.questions) {
|
||||
message.warning('请先输入问题');
|
||||
return;
|
||||
}
|
||||
if (formVal.questions?.filter(Boolean).length === 0) {
|
||||
message.warning('请先输入问题');
|
||||
return;
|
||||
}
|
||||
const questions = formVal.questions?.filter(Boolean).map((item: any) => item.question);
|
||||
await addQuestionRun(questions);
|
||||
}}
|
||||
loading={addLoading}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Form form={form}>
|
||||
<Form.List name="questions">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name }) => (
|
||||
<div key={key} className={cls('flex flex-1 items-center gap-8')}>
|
||||
<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 />}
|
||||
>
|
||||
添加问题
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -8,9 +8,10 @@ import DocUploadForm from '@/components/knowledge/doc-upload-form';
|
||||
import Segmentation from '@/components/knowledge/segmentation';
|
||||
import SpaceForm from '@/components/knowledge/space-form';
|
||||
import { File, ISpace, StepChangeParams } from '@/types/knowledge';
|
||||
import { PlusOutlined, ReadOutlined, WarningOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, Steps, Tag } from 'antd';
|
||||
import { PlusOutlined, ReadOutlined, SearchOutlined, WarningOutlined } from '@ant-design/icons';
|
||||
import { Button, Input, Modal, Spin, Steps, Tag } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { debounce } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
@@ -28,6 +29,7 @@ const Knowledge = () => {
|
||||
const [files, setFiles] = useState<Array<File>>([]);
|
||||
const [docType, setDocType] = useState<string>('');
|
||||
const [addStatus, setAddStatus] = useState<string>('');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const addKnowledgeSteps = [
|
||||
@@ -38,8 +40,10 @@ const Knowledge = () => {
|
||||
];
|
||||
const router = useRouter();
|
||||
|
||||
async function getSpaces() {
|
||||
const [_, data] = await apiInterceptors(getSpaceList());
|
||||
async function getSpaces(params?: any) {
|
||||
setLoading(true);
|
||||
const [_, data] = await apiInterceptors(getSpaceList({ ...params }));
|
||||
setLoading(false);
|
||||
setSpaceList(data);
|
||||
}
|
||||
useEffect(() => {
|
||||
@@ -108,138 +112,143 @@ const Knowledge = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const onSearch = async (e: any) => {
|
||||
getSpaces({ name: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<ConstructLayout>
|
||||
<div className="page-body p-4 md:p-6 h-[90vh] overflow-auto">
|
||||
{/* <Button
|
||||
type="primary"
|
||||
className="flex items-center"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setIsAddShow(true);
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button> */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* <Input
|
||||
variant="filled"
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder={t('please_enter_the_keywords')}
|
||||
// onChange={onSearch}
|
||||
// onPressEnter={onSearch}
|
||||
allowClear
|
||||
className="w-[230px] h-[40px] border-1 border-white backdrop-filter backdrop-blur-lg bg-white bg-opacity-30 dark:border-[#6f7f95] dark:bg-[#6f7f95] dark:bg-opacity-60"
|
||||
/> */}
|
||||
</div>
|
||||
<Spin spinning={loading}>
|
||||
<div className="page-body p-4 md:p-6 h-[90vh] overflow-auto">
|
||||
{/* <Button
|
||||
type="primary"
|
||||
className="flex items-center"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setIsAddShow(true);
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button> */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
variant="filled"
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder={t('please_enter_the_keywords')}
|
||||
onChange={debounce(onSearch, 300)}
|
||||
allowClear
|
||||
className="w-[230px] h-[40px] border-1 border-white backdrop-filter backdrop-blur-lg bg-white bg-opacity-30 dark:border-[#6f7f95] dark:bg-[#6f7f95] dark:bg-opacity-60"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
className="border-none text-white bg-button-gradient"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setIsAddShow(true);
|
||||
}}
|
||||
>
|
||||
{t('create_knowledge')}
|
||||
</Button>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
className="border-none text-white bg-button-gradient"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setIsAddShow(true);
|
||||
}}
|
||||
>
|
||||
{t('create_knowledge')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap mt-4 mx-[-8px]">
|
||||
{spaceList?.map((space: ISpace) => (
|
||||
<BlurredCard
|
||||
onClick={() => {
|
||||
setCurrentSpace(space);
|
||||
setIsPanelShow(true);
|
||||
localStorage.setItem('cur_space_id', JSON.stringify(space.id));
|
||||
}}
|
||||
description={space.desc}
|
||||
name={space.name}
|
||||
key={space.id}
|
||||
logo="/images/knowledge.png"
|
||||
RightTop={
|
||||
<InnerDropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'del',
|
||||
label: (
|
||||
<span className="text-red-400" onClick={() => showDeleteConfirm(space)}>
|
||||
删除
|
||||
</span>
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
}
|
||||
rightTopHover={false}
|
||||
Tags={
|
||||
<div>
|
||||
<Tag>
|
||||
<span className="flex items-center gap-1">
|
||||
<ReadOutlined className="mt-[1px]" />
|
||||
{space.docs}
|
||||
</span>
|
||||
</Tag>
|
||||
</div>
|
||||
}
|
||||
LeftBottom={
|
||||
<div className="flex gap-2">
|
||||
<span>{space.owner}</span>
|
||||
<span>•</span>
|
||||
{space?.gmt_modified && <span>{moment(space?.gmt_modified).fromNow() + ' ' + t('update')}</span>}
|
||||
</div>
|
||||
}
|
||||
RightBottom={
|
||||
<ChatButton
|
||||
text={t('start_chat')}
|
||||
onClick={() => {
|
||||
handleChat(space);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap mt-4 mx-[-8px]">
|
||||
{spaceList?.map((space: ISpace) => (
|
||||
<BlurredCard
|
||||
onClick={() => {
|
||||
setCurrentSpace(space);
|
||||
setIsPanelShow(true);
|
||||
localStorage.setItem('cur_space_id', JSON.stringify(space.id));
|
||||
}}
|
||||
description={space.desc}
|
||||
name={space.name}
|
||||
key={space.id}
|
||||
logo="/images/knowledge.png"
|
||||
RightTop={
|
||||
<InnerDropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'del',
|
||||
label: (
|
||||
<span className="text-red-400" onClick={() => showDeleteConfirm(space)}>
|
||||
删除
|
||||
</span>
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
}
|
||||
rightTopHover={false}
|
||||
Tags={
|
||||
<div>
|
||||
<Tag>
|
||||
<span className="flex items-center gap-1">
|
||||
<ReadOutlined className="mt-[1px]" />
|
||||
{space.docs}
|
||||
</span>
|
||||
</Tag>
|
||||
</div>
|
||||
}
|
||||
LeftBottom={
|
||||
<div className="flex gap-2">
|
||||
<span>{space.owner}</span>
|
||||
<span>•</span>
|
||||
{space?.gmt_modified && <span>{moment(space?.gmt_modified).fromNow() + ' ' + t('update')}</span>}
|
||||
</div>
|
||||
}
|
||||
RightBottom={
|
||||
<ChatButton
|
||||
text={t('start_chat')}
|
||||
onClick={() => {
|
||||
handleChat(space);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
className="h-5/6 overflow-hidden"
|
||||
open={isPanelShow}
|
||||
width={'70%'}
|
||||
onCancel={() => setIsPanelShow(false)}
|
||||
footer={null}
|
||||
destroyOnClose={true}
|
||||
>
|
||||
<DocPanel space={currentSpace!} onAddDoc={onAddDoc} onDeleteDoc={getSpaces} addStatus={addStatus} />
|
||||
</Modal>
|
||||
<Modal
|
||||
title="新增知识库"
|
||||
centered
|
||||
open={isAddShow}
|
||||
destroyOnClose={true}
|
||||
onCancel={() => {
|
||||
setIsAddShow(false);
|
||||
}}
|
||||
width={1000}
|
||||
afterClose={() => {
|
||||
setActiveStep(0);
|
||||
getSpaces();
|
||||
}}
|
||||
footer={null}
|
||||
>
|
||||
<Steps current={activeStep} items={addKnowledgeSteps} />
|
||||
{activeStep === 0 && <SpaceForm handleStepChange={handleStepChange} />}
|
||||
{activeStep === 1 && <DocTypeForm handleStepChange={handleStepChange} />}
|
||||
<DocUploadForm
|
||||
className={classNames({ hidden: activeStep !== 2 })}
|
||||
spaceName={spaceName}
|
||||
docType={docType}
|
||||
handleStepChange={handleStepChange}
|
||||
/>
|
||||
{activeStep === 3 && <Segmentation spaceName={spaceName} docType={docType} uploadFiles={files} handleStepChange={handleStepChange} />}
|
||||
</Modal>
|
||||
<Modal
|
||||
className="h-5/6 overflow-hidden"
|
||||
open={isPanelShow}
|
||||
width={'70%'}
|
||||
onCancel={() => setIsPanelShow(false)}
|
||||
footer={null}
|
||||
destroyOnClose={true}
|
||||
>
|
||||
<DocPanel space={currentSpace!} onAddDoc={onAddDoc} onDeleteDoc={getSpaces} addStatus={addStatus} />
|
||||
</Modal>
|
||||
<Modal
|
||||
title="新增知识库"
|
||||
centered
|
||||
open={isAddShow}
|
||||
destroyOnClose={true}
|
||||
onCancel={() => {
|
||||
setIsAddShow(false);
|
||||
}}
|
||||
width={1000}
|
||||
afterClose={() => {
|
||||
setActiveStep(0);
|
||||
getSpaces();
|
||||
}}
|
||||
footer={null}
|
||||
>
|
||||
<Steps current={activeStep} items={addKnowledgeSteps} />
|
||||
{activeStep === 0 && <SpaceForm handleStepChange={handleStepChange} />}
|
||||
{activeStep === 1 && <DocTypeForm handleStepChange={handleStepChange} />}
|
||||
<DocUploadForm
|
||||
className={classNames({ hidden: activeStep !== 2 })}
|
||||
spaceName={spaceName}
|
||||
docType={docType}
|
||||
handleStepChange={handleStepChange}
|
||||
/>
|
||||
{activeStep === 3 && <Segmentation spaceName={spaceName} docType={docType} uploadFiles={files} handleStepChange={handleStepChange} />}
|
||||
</Modal>
|
||||
</Spin>
|
||||
</ConstructLayout>
|
||||
);
|
||||
};
|
||||
|
@@ -3,6 +3,8 @@ import { ChatContext } from '@/app/chat-context';
|
||||
import { addPrompt, apiInterceptors, llmOutVerify, promptTemplateLoad, promptTypeTarget, updatePrompt } from '@/client/api';
|
||||
import useUser from '@/hooks/use-user';
|
||||
import { DebugParams, OperatePromptParams } from '@/types/prompt';
|
||||
import { getUserId } from '@/utils';
|
||||
import { HEADER_USER_ID_KEY } from '@/utils/constants/index';
|
||||
import { LeftOutlined } from '@ant-design/icons';
|
||||
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import JsonView from '@uiw/react-json-view';
|
||||
@@ -238,6 +240,7 @@ const AddOrEditPrompt: React.FC = () => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
[HEADER_USER_ID_KEY]: getUserId() ?? '',
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
openWhenHidden: true,
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import { apiInterceptors, clearChatHistory } from '@/client/api';
|
||||
import { ChatHistoryResponse } from '@/types/chat';
|
||||
import { getUserId } from '@/utils';
|
||||
import { HEADER_USER_ID_KEY } from '@/utils/constants/index';
|
||||
import { ClearOutlined, LoadingOutlined, PauseCircleOutlined, RedoOutlined, SendOutlined } from '@ant-design/icons';
|
||||
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import { useRequest } from 'ahooks';
|
||||
@@ -71,6 +73,7 @@ const InputContainer: React.FC = () => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
[HEADER_USER_ID_KEY]: getUserId() ?? '',
|
||||
},
|
||||
signal: ctrl.current.signal,
|
||||
body: JSON.stringify(params),
|
||||
|
@@ -3,6 +3,8 @@ import { apiInterceptors, getAppInfo, getChatHistory, getDialogueList, postChatM
|
||||
import useUser from '@/hooks/use-user';
|
||||
import { IApp } from '@/types/app';
|
||||
import { ChatHistoryResponse } from '@/types/chat';
|
||||
import { getUserId } from '@/utils';
|
||||
import { HEADER_USER_ID_KEY } from '@/utils/constants/index';
|
||||
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Spin } from 'antd';
|
||||
@@ -225,6 +227,7 @@ const MobileChat: React.FC = () => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
[HEADER_USER_ID_KEY]: getUserId() ?? '',
|
||||
},
|
||||
signal: ctrl.current.signal,
|
||||
body: JSON.stringify(params),
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 21 KiB |
@@ -114,6 +114,7 @@ export type ChunkListParams = {
|
||||
document_id?: string | number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
content?: string;
|
||||
};
|
||||
|
||||
export type IChunk = {
|
||||
@@ -156,6 +157,10 @@ export type SummaryParams = {
|
||||
conv_uid: string;
|
||||
};
|
||||
|
||||
export interface SearchDocumentParams {
|
||||
doc_name?: string;
|
||||
status?: string;
|
||||
}
|
||||
export interface AddYuqueProps {
|
||||
doc_name: string;
|
||||
content: string;
|
||||
@@ -164,3 +169,17 @@ export interface AddYuqueProps {
|
||||
space_name: string;
|
||||
questions?: string[];
|
||||
}
|
||||
|
||||
export interface RecallTestChunk {
|
||||
chunk_id: number;
|
||||
content: string;
|
||||
metadata: Record<string, any>;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface RecallTestProps {
|
||||
question: string;
|
||||
recall_score_threshold?: number;
|
||||
recall_top_k?: number;
|
||||
recall_retrievers: string[];
|
||||
}
|
||||
|
1
web_new/utils/constants/header.ts
Normal file
1
web_new/utils/constants/header.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const HEADER_USER_ID_KEY = 'user-id';
|
@@ -1,9 +1,11 @@
|
||||
import { message } from 'antd';
|
||||
import axios from './ctx-axios';
|
||||
import { isPlainObject } from 'lodash';
|
||||
import { getUserId } from '@/utils';
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
'content-type': 'application/json',
|
||||
'User-Id': getUserId(),
|
||||
};
|
||||
|
||||
// body 字段 trim
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { STORAGE_INIT_MESSAGE_KET } from './constants/index';
|
||||
import { STORAGE_INIT_MESSAGE_KET, STORAGE_USERINFO_KEY } from './constants/index';
|
||||
|
||||
export function getInitMessage() {
|
||||
const value = localStorage.getItem(STORAGE_INIT_MESSAGE_KET) ?? '';
|
||||
@@ -9,3 +9,12 @@ export function getInitMessage() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getUserId(): string | undefined {
|
||||
try {
|
||||
const id = JSON.parse(localStorage.getItem(STORAGE_USERINFO_KEY) ?? '')['user_id'];
|
||||
return id;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user