diff --git a/dbgpt/app/openapi/api_v1/api_v1.py b/dbgpt/app/openapi/api_v1/api_v1.py index 43740b636..f9a98bbdd 100644 --- a/dbgpt/app/openapi/api_v1/api_v1.py +++ b/dbgpt/app/openapi/api_v1/api_v1.py @@ -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: diff --git a/dbgpt/client/_cli.py b/dbgpt/client/_cli.py index 3dd29bbbe..269c83c40 100644 --- a/dbgpt/client/_cli.py +++ b/dbgpt/client/_cli.py @@ -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) diff --git a/dbgpt/core/awel/operators/base.py b/dbgpt/core/awel/operators/base.py index 58f3acabc..aafa11f90 100644 --- a/dbgpt/core/awel/operators/base.py +++ b/dbgpt/core/awel/operators/base.py @@ -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 diff --git a/dbgpt/model/cluster/controller/controller.py b/dbgpt/model/cluster/controller/controller.py index 2ff1c7d98..86298ea6b 100644 --- a/dbgpt/model/cluster/controller/controller.py +++ b/dbgpt/model/cluster/controller/controller.py @@ -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) diff --git a/dbgpt/model/cluster/registry_impl/storage.py b/dbgpt/model/cluster/registry_impl/storage.py index 6cafed7cc..18b13859e 100644 --- a/dbgpt/model/cluster/registry_impl/storage.py +++ b/dbgpt/model/cluster/registry_impl/storage.py @@ -72,7 +72,6 @@ class ModelInstanceIdentifier(ResourceIdentifier): @dataclass class ModelInstanceStorageItem(StorageItem): - model_name: str host: str port: int diff --git a/dbgpt/util/api_utils.py b/dbgpt/util/api_utils.py index 3312aaafc..07206d5e0 100644 --- a/dbgpt/util/api_utils.py +++ b/dbgpt/util/api_utils.py @@ -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]: diff --git a/web_new/ant-components/layout/UserBar.tsx b/web_new/ant-components/layout/UserBar.tsx index 2c44756e5..a9b63df4b 100644 --- a/web_new/ant-components/layout/UserBar.tsx +++ b/web_new/ant-components/layout/UserBar.tsx @@ -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 ( diff --git a/web_new/app/chat-context.tsx b/web_new/app/chat-context.tsx index ab824032f..5d3cf0bd9 100644 --- a/web_new/app/chat-context.tsx +++ b/web_new/app/chat-context.tsx @@ -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 { diff --git a/web_new/client/api/evaluate/index.ts b/web_new/client/api/evaluate/index.ts index 38a0a37b4..80224d512 100644 --- a/web_new/client/api/evaluate/index.ts +++ b/web_new/client/api/evaluate/index.ts @@ -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>(`/api/v1/evaluate/datasets`, data); + return GET>(`/api/v1/evaluate/datasets`, data, { + headers: { + 'user-id': userId, + }, + }); }; export const uploadDataSets = (data: uploadDataSetsRequest) => { return POST>(`/api/v1/evaluate/dataset/upload`, data, { headers: { + 'user-id': userId, 'Content-Type': 'multipart/form-data', }, }); }; export const uploadDataSetsContent = (data: uploadDataSetsRequest) => { - return POST>(`/api/v1/evaluate/dataset/upload/content`, data); + return POST>(`/api/v1/evaluate/dataset/upload/content`, data, { + headers: { + 'user-id': userId, + }, + }); }; export const uploadDataSetsFile = (data: FormData) => { return POST>(`/api/v1/evaluate/dataset/upload/file`, data, { headers: { + 'user-id': userId, 'Content-Type': 'multipart/form-data', }, }); }; export const delDataSet = (params: delDataSetRequest) => { - return DELETE>(`/api/v1/evaluate/dataset`, params); + return DELETE>(`/api/v1/evaluate/dataset`, params, { + headers: { + 'user-id': userId, + }, + }); }; //download dataSet export const downloadDataSet = (params: delDataSetRequest) => { return GET(`/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(`/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>(`/api/v1/evaluate/evaluation`, params); + return DELETE>(`/api/v1/evaluate/evaluation`, params, { + headers: { + 'user-id': userId, + }, + }); }; //get evaluations export const getEvaluations = (data: getEvaluationsRequest) => { - return GET>(`/api/v1/evaluate/evaluations`, data); + return GET>(`/api/v1/evaluate/evaluations`, data, { + headers: { + 'user-id': userId, + }, + }); }; export const getMetrics = (data: getMetricsRequest) => { - return GET>(`/api/v1/evaluate/metrics`, data); + return GET>(`/api/v1/evaluate/metrics`, data, { + headers: { + 'user-id': userId, + }, + }); }; export const showEvaluation = (data: Partial) => { - return GET, Record[]>(`/api/v1/evaluate/evaluation/detail/show`, data); + return GET, Record[]>(`/api/v1/evaluate/evaluation/detail/show`, data, { + headers: { + 'user-id': userId, + }, + }); }; export const getStorageTypes = () => { - return GET>(`/api/v1/evaluate/storage/types`, undefined); + return GET>(`/api/v1/evaluate/storage/types`, undefined, { + headers: { + 'user-id': userId, + }, + }); }; //create evaluations export const createEvaluations = (data: createEvaluationsRequest) => { - return POST>(`/api/v1/evaluate/start`, data); + return POST>(`/api/v1/evaluate/start`, data, { + headers: { + 'user-id': userId, + }, + }); }; //update evaluations export const updateEvaluations = (data: updateDataSetRequest) => { - return POST>(`/api/v1/evaluate/dataset/members/update`, data); + return POST>(`/api/v1/evaluate/dataset/members/update`, data, { + headers: { + 'user-id': userId, + }, + }); }; // export const cancelFeedback = (data: CancelFeedbackAddParams) => { diff --git a/web_new/client/api/index.ts b/web_new/client/api/index.ts index 7a4172010..d345438db 100644 --- a/web_new/client/api/index.ts +++ b/web_new/client/api/index.ts @@ -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 = { @@ -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; }); diff --git a/web_new/client/api/knowledge/index.ts b/web_new/client/api/knowledge/index.ts index a7a0f1f99..7f9d77ec4 100644 --- a/web_new/client/api/knowledge/index.ts +++ b/web_new/client/api/knowledge/index.ts @@ -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(`/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(`/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); +}; diff --git a/web_new/client/api/request.ts b/web_new/client/api/request.ts index d2b9c3213..ca718c97f 100644 --- a/web_new/client/api/request.ts +++ b/web_new/client/api/request.ts @@ -155,8 +155,8 @@ export const saveArguments = (knowledgeName: string, data: ArgumentsParams) => { return POST(`/knowledge/${knowledgeName}/argument/save`, data); }; -export const getSpaceList = () => { - return POST>('/knowledge/space/list', {}); +export const getSpaceList = (data: any) => { + return POST>('/knowledge/space/list', data); }; export const getDocumentList = (spaceName: number, data: Record>) => { return POST>, IDocumentResponse>(`/knowledge/${spaceName}/document/list`, data); diff --git a/web_new/components/MenuModal/index.tsx b/web_new/components/MenuModal/index.tsx new file mode 100644 index 000000000..c52343898 --- /dev/null +++ b/web_new/components/MenuModal/index.tsx @@ -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 ( + +
+
+ { + setCurrentMenuKey(info.key); + }} + inlineCollapsed={false} + items={items.map((item) => ({ key: item.key, label: item.label }))} + /> +
+
+ {items.map((item) => { + if (item.key === currentMenuKey) { + return {item.children}; + } + })} +
+
+
+ ); +} + +export default MenuModal; diff --git a/web_new/components/chat/chat-content/vis-chart.tsx b/web_new/components/chat/chat-content/vis-chart.tsx index 61b961248..1c5cb8070 100644 --- a/web_new/components/chat/chat-content/vis-chart.tsx +++ b/web_new/components/chat/chat-content/vis-chart.tsx @@ -13,7 +13,10 @@ interface Props { } function VisChart({ data }: Props) { - return ; + if (!data) { + return null; + } + return ; } export default VisChart; diff --git a/web_new/components/knowledge/RecallTestModal.tsx b/web_new/components/knowledge/RecallTestModal.tsx new file mode 100644 index 000000000..b553b5077 --- /dev/null +++ b/web_new/components/knowledge/RecallTestModal.tsx @@ -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>; + space: ISpace; +}; + +const tagColors = ['magenta', 'orange', 'geekblue', 'purple', 'cyan', 'green']; + +const RecallTestModal: React.FC = ({ 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 ( + setOpen(false)} centered destroyOnClose={true}> + + + + + + + + + + + {/* {questions?.length > 0 && ( + + +
+ {questions.map((item, index) => ( + { + form.setFieldValue('question', item); + }} + > + {item} + + ))} +
+
+ + )} */} + +
+ + + {resultList.length > 0 ? ( +
+ {resultList.map((item) => ( + + # {item.chunk_id} + {item.metadata.prop_field.title} +
+ } + extra={ +
+ score: + {item.score} +
+ } + key={item.chunk_id} + size="small" + className="mb-4 border-gray-500 shadow-md" + > + {item.content} +
+ ))} + + ) : ( + + )} + + +
+ ); +}; + +export default RecallTestModal; diff --git a/web_new/components/knowledge/doc-panel.tsx b/web_new/components/knowledge/doc-panel.tsx index 4a4322e37..ac7a0802e 100644 --- a/web_new/components/knowledge/doc-panel.tsx +++ b/web_new/components/knowledge/doc-panel.tsx @@ -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(false); const [curDoc, setCurDoc] = useState(); + // 召回测试弹窗 + const [recallTestOpen, setRecallTestOpen] = useState(false); + const currentPageRef = useRef(1); const hasMore = useMemo(() => { @@ -440,6 +445,9 @@ export default function DocPanel(props: IProps) { + {renderDocumentCard()} @@ -517,6 +525,8 @@ export default function DocPanel(props: IProps) { + {/* 召回测试弹窗 */} + ); } diff --git a/web_new/hooks/use-chat.ts b/web_new/hooks/use-chat.ts index 4f5f80145..c973c9ab2 100644 --- a/web_new/hooks/use-chat.ts +++ b/web_new/hooks/use-chat.ts @@ -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({} 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, diff --git a/web_new/next.config.js b/web_new/next.config.js index dc35e8fd4..4d36aa571 100644 --- a/web_new/next.config.js +++ b/web_new/next.config.js @@ -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, diff --git a/web_new/pages/_app.tsx b/web_new/pages/_app.tsx index 2c29386f6..f9c755328 100644 --- a/web_new/pages/_app.tsx +++ b/web_new/pages/_app.tsx @@ -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}; diff --git a/web_new/pages/chat/index.tsx b/web_new/pages/chat/index.tsx index ad0c6068c..e3faebf42 100644 --- a/web_new/pages/chat/index.tsx +++ b/web_new/pages/chat/index.tsx @@ -68,7 +68,7 @@ export const ChatContentContext = createContext({ 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') ?? ''; diff --git a/web_new/pages/construct/knowledge/chunk/index.tsx b/web_new/pages/construct/knowledge/chunk/index.tsx index 2ecbe33a6..9e7e83607 100644 --- a/web_new/pages/construct/knowledge/chunk/index.tsx +++ b/web_new/pages/construct/knowledge/chunk/index.tsx @@ -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([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); - const [isExpand, setIsExpand] = useState(false); + // const [isExpand, setIsExpand] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const [currentChunkInfo, setCurrentChunkInfo] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + + const [pageSize, setPageSize] = useState(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 (
+
+ } + placeholder={t('please_enter_the_keywords')} + onChange={debounce(onSearch, 300)} + allowClear + /> +
{chunkList?.length > 0 ? (
- {chunkList?.map((chunk: any) => { + {chunkList?.map((chunk: any, index: number) => { return ( - - {chunk.doc_name} + + # {index + (currentPage - 1) * DEDAULT_PAGE_SIZE} + {/* */} + {chunk.doc_name} } 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); + }} >

{t('Content')}:

{chunk?.content}

{t('Meta_Data')}:

{chunk?.meta_info}

- setIsExpand(!isExpand)} > {isExpand ? '收起' : '展开'} - + */}
); })} @@ -113,6 +174,105 @@ function ChunkList() { showTotal={(total) => `Total ${total} items`} onChange={loaderMore} /> + setIsModalOpen(false), + afterOpenChange: (open) => { + if (open) { + form.setFieldValue( + 'questions', + JSON.parse(currentChunkInfo?.questions || '[]')?.map((item: any) => ({ question: item })), + ); + } + }, + }} + items={[ + { + key: 'edit', + label: '数据内容', + children: ( +
+ + {currentChunkInfo?.content} + + + {currentChunkInfo?.meta_info} + +
+ ), + }, + { + key: 'delete', + label: '添加问题', + children: ( + { + 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} + > + 保存 + + } + > +
+ + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name }) => ( +
+ + + + + { + remove(name); + }} + /> + +
+ ))} + + + + + )} +
+
+
+ ), + }, + ]} + />
); } diff --git a/web_new/pages/construct/knowledge/index.tsx b/web_new/pages/construct/knowledge/index.tsx index 827827f7b..ad865a8ce 100644 --- a/web_new/pages/construct/knowledge/index.tsx +++ b/web_new/pages/construct/knowledge/index.tsx @@ -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>([]); const [docType, setDocType] = useState(''); const [addStatus, setAddStatus] = useState(''); + const [loading, setLoading] = useState(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 ( -
- {/* */} -
-
- {/* } - 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" - /> */} -
+ +
+ {/* */} +
+
+ } + 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" + /> +
-
- +
+ +
+
+
+ {spaceList?.map((space: ISpace) => ( + { + 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={ + showDeleteConfirm(space)}> + 删除 + + ), + }, + ], + }} + /> + } + rightTopHover={false} + Tags={ +
+ + + + {space.docs} + + +
+ } + LeftBottom={ +
+ {space.owner} + + {space?.gmt_modified && {moment(space?.gmt_modified).fromNow() + ' ' + t('update')}} +
+ } + RightBottom={ + { + handleChat(space); + }} + /> + } + /> + ))}
-
- {spaceList?.map((space: ISpace) => ( - { - 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={ - showDeleteConfirm(space)}> - 删除 - - ), - }, - ], - }} - /> - } - rightTopHover={false} - Tags={ -
- - - - {space.docs} - - -
- } - LeftBottom={ -
- {space.owner} - - {space?.gmt_modified && {moment(space?.gmt_modified).fromNow() + ' ' + t('update')}} -
- } - RightBottom={ - { - handleChat(space); - }} - /> - } - /> - ))} -
-
- setIsPanelShow(false)} - footer={null} - destroyOnClose={true} - > - - - { - setIsAddShow(false); - }} - width={1000} - afterClose={() => { - setActiveStep(0); - getSpaces(); - }} - footer={null} - > - - {activeStep === 0 && } - {activeStep === 1 && } - - {activeStep === 3 && } - + setIsPanelShow(false)} + footer={null} + destroyOnClose={true} + > + + + { + setIsAddShow(false); + }} + width={1000} + afterClose={() => { + setActiveStep(0); + getSpaces(); + }} + footer={null} + > + + {activeStep === 0 && } + {activeStep === 1 && } + + {activeStep === 3 && } + +
); }; diff --git a/web_new/pages/construct/prompt/[type]/index.tsx b/web_new/pages/construct/prompt/[type]/index.tsx index 9a9696e32..fd2336cbb 100644 --- a/web_new/pages/construct/prompt/[type]/index.tsx +++ b/web_new/pages/construct/prompt/[type]/index.tsx @@ -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, diff --git a/web_new/pages/mobile/chat/components/InputContainer.tsx b/web_new/pages/mobile/chat/components/InputContainer.tsx index fe318a452..4723686b3 100644 --- a/web_new/pages/mobile/chat/components/InputContainer.tsx +++ b/web_new/pages/mobile/chat/components/InputContainer.tsx @@ -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), diff --git a/web_new/pages/mobile/chat/index.tsx b/web_new/pages/mobile/chat/index.tsx index eb2858d46..7c24cdecc 100644 --- a/web_new/pages/mobile/chat/index.tsx +++ b/web_new/pages/mobile/chat/index.tsx @@ -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), diff --git a/web_new/public/logo_s_latest.png b/web_new/public/logo_s_latest.png index 472bc799c..801274c3c 100644 Binary files a/web_new/public/logo_s_latest.png and b/web_new/public/logo_s_latest.png differ diff --git a/web_new/public/logo_zh_latest.png b/web_new/public/logo_zh_latest.png index 2ccedfcfe..914aa930e 100644 Binary files a/web_new/public/logo_zh_latest.png and b/web_new/public/logo_zh_latest.png differ diff --git a/web_new/types/knowledge.ts b/web_new/types/knowledge.ts index b0168e16f..e0cbb22a5 100644 --- a/web_new/types/knowledge.ts +++ b/web_new/types/knowledge.ts @@ -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; + score: number; +} + +export interface RecallTestProps { + question: string; + recall_score_threshold?: number; + recall_top_k?: number; + recall_retrievers: string[]; +} diff --git a/web_new/utils/constants/header.ts b/web_new/utils/constants/header.ts new file mode 100644 index 000000000..4fad5e25e --- /dev/null +++ b/web_new/utils/constants/header.ts @@ -0,0 +1 @@ +export const HEADER_USER_ID_KEY = 'user-id'; diff --git a/web_new/utils/request.ts b/web_new/utils/request.ts index 102b7fdbd..fa1706e23 100644 --- a/web_new/utils/request.ts +++ b/web_new/utils/request.ts @@ -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 diff --git a/web_new/utils/storage.ts b/web_new/utils/storage.ts index 690a69845..4f542f3b4 100644 --- a/web_new/utils/storage.ts +++ b/web_new/utils/storage.ts @@ -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; + } +}