Explorar o código

Merge branch 'app-permission' of https://git.zyuas.com/LLM/chat-client-web into app-permission

* 'app-permission' of https://git.zyuas.com/LLM/chat-client-web:
  新增直接打开聊天页面
  hebing
  切换生产环境
  修复问答历史
  链接本地
  智普问题修复
  ok
Ryuiso hai 4 meses
pai
achega
1c6e233ff4

+ 1 - 1
.gitignore

@@ -4,7 +4,7 @@
 /node_modules
 /.pnp
 .pnp.js
-
+local.md
 # testing
 /coverage
 

+ 27 - 0
README.md

@@ -122,3 +122,30 @@ sequenceDiagram
 1. **插件系统**: 支持功能模块动态扩展
 2. **配置中心**: 运行时配置热更新
 3. **多模型路由**: 根据请求自动路由到最优AI服务
+
+# 智普问答 可以访问的地址
+http://localhost:4000/#/knowledgeChat?showMenu=true&chatMode=LOCAL&appId=2924812721300312064
+http://localhost:4000/#/knowledgeChat?showMenu=false&chatMode=LOCAL&appId=2924812721300312064
+
+常改文件---components/chat.tsx--->对应的是智普问答
+常改文件---components/DeepSeekChat.tsx--->对应的是deepSeek问答
+
+本地调试需要改变文件
+1、app/client/platforms/deepSeek.ts
+    this.apiPath = this.baseURL + '/vllm/ai/chat';//线上地址
+    this.apiPath = this.baseURL + '/vllm/chat'; // 测试地址
+2、 destination: "http://xia0miduo.gicp.net:8401/:path*",----这一步可以改也可以不改
+        // destination: "http://xia0miduo.gicp.net:8401/:path*",
+        destination: "http://192.168.3.123:8091/:path*",
+3、app/components/home.tsx 
+    toUninLogin方法下增加 return 禁止跳转到首页
+    如下:
+    const toUninLogin = async (originUrl:string, fullUrl:string) => {
+    // return ----- 这里
+    //测试环境
+    //const loginUrl = 'https://esctest.sribs.com.cn/esc-sso/oauth2.0/authorize?client_id=e97f94cf93761f4d69e8&response_type=code';
+    //生产环境
+    const loginUrl = 'http://esc.sribs.com.cn:8080/esc-sso/oauth2.0/authorize?client_id=e97f94cf93761f4d69e8&response_type=code';
+    const externalLoginUrl = loginUrl + `&redirect_uri=${encodeURIComponent(originUrl)}&state=${encodeURIComponent(fullUrl)}`;
+    location.replace(externalLoginUrl);
+  }

+ 17 - 2
app/client/platforms/bigModel.ts

@@ -152,7 +152,7 @@ export class BigModelApi implements LLMApi {
         },
         onmessage: async (msg) => {
           const info = JSON.parse(msg.data);
-          if (info.event === 'finish') {
+          if (info.event === 'finish') { // 完成
             const chatMode = useChatStore.getState().chatMode;
             if (chatMode === 'LOCAL') {// 切片
               useChatStore.getState().updateCurrentSession((se) => {
@@ -161,8 +161,23 @@ export class BigModelApi implements LLMApi {
               sliceInfoPromise = (async () => {
                 try {
                   const res: any = await api.get(`deepseek/api/slice/search/${info.id}`);
+                  let allChunkNum = 0;
+                  delete res.data.code;
+                  const values1 = Object.keys(res.data).reduce((acc, knowledge_id) => {
+                  const docs = res.data[knowledge_id];
+                  const transformedDocs = docs.map((doc: any) =>{
+                    allChunkNum += doc.chunk_nums;
+                      return{
+                        knowledge_id,
+                        ...doc
+                      }
+                    });
+                    return acc.concat(transformedDocs);
+                  }, []);
                   const sliceInfo = {
+                    allChunkNum:allChunkNum,
                     ...res.data,
+                    doc: values1
                   };
                   delete sliceInfo.code;
                   useChatStore.getState().updateCurrentSession((session) => {
@@ -190,7 +205,7 @@ export class BigModelApi implements LLMApi {
           // 获取当前的数据
           const currentData = info.data;
           const formatStart = '```think';
-          const formatEnd = 'think```';
+          const formatEnd = 'think```'; 
 
           if (currentData?.startsWith(formatStart)) {
             remainText += currentData.replace(formatStart, '```think\n');

+ 2 - 1
app/client/platforms/deepSeek.ts

@@ -22,7 +22,8 @@ export class DeepSeekApi implements LLMApi {
   constructor() {
     // this.baseURL = 'http://192.168.3.209:18078';
     this.baseURL = '/deepseek-api';
-    this.apiPath = this.baseURL + '/vllm/ai/chat';
+    this.apiPath = this.baseURL + '/vllm/ai/chat';//线上地址
+    // this.apiPath = this.baseURL + '/vllm/chat'; // 测试地址 
   }
 
   async chat(options: ChatOptions) {

+ 65 - 4
app/components/chat.tsx

@@ -678,10 +678,69 @@ export function ChatActions(props: {
       setGuessList([]);
     }
   }, [props.isClickStop])
+  
+  const [activeKey, setActiveKey]= useState('0'); // 控制手风琴是否展示
 
   return (
     <div className={styles["chat-input-actions"]}>
       {
+        props.sendStatus &&
+        <Collapse
+          accordion={true}
+          activeKey={activeKey}
+          onChange={(key)=>{setActiveKey(key[0])}}
+          bordered={false}
+          style={{ width: '100%',backgroundColor:'#fff' }}
+          expandIconPosition="end"
+          items={[
+            {
+              key: '1',
+              label:<span style={{ color: '#8096ca' }}>你还可以尝试提问:</span>,
+              children: <div style={{ color: '#8096ca', fontSize: 13, overflowX: 'auto' }}>
+                  {/* <div>
+                    你还可以尝试提问:
+                  </div> */}
+                  
+                  {
+                    guessList.length === 0 ?
+                      <Space style={{ margin: '10px 0' }}>
+                        <Skeleton.Button size="small" active={true} />
+                        <Skeleton.Button size="small" active={true} />
+                        <Skeleton.Button size="small" active={true} />
+                      </Space>
+                      :
+                      <div style={{ display: 'flex', margin: '10px 0', overflowX: 'auto' }}>
+                        {
+                          guessList.map((item, index) => {
+                            return (
+                              <div
+                                style={{
+                                  padding: '5px 10px',
+                                  background: '#f2f4f8',
+                                  borderRadius: 5,
+                                  margin: '0 10px 10px 0',
+                                  cursor: 'pointer',
+                                }}
+                                onClick={() => {
+                                  props.setUserInput(item);
+                                  props.doSubmit(item)
+                                  setActiveKey('')
+                                }}
+                                key={index}
+                              >
+                                {item}
+                              </div>
+                            )
+                          })
+                        }
+                      </div>
+                  }
+              </div>
+            }
+          ]}
+        />
+      }
+      {/* {
         props.sendStatus &&
         <div style={{ color: '#8096ca', fontSize: 13, overflowX: 'auto' }}>
           <div>
@@ -721,7 +780,7 @@ export function ChatActions(props: {
               </div>
           }
         </div>
-      }
+      } */}
       {/* {couldStop && (
         <ChatAction
           onClick={stopAll}
@@ -1921,6 +1980,7 @@ function _Chat() {
           setAutoScroll(false);
         }}
       >
+        {/* 这里是具体的聊天框 */}
         {
           messages.length > 1 ?
             <>
@@ -2042,9 +2102,10 @@ function _Chat() {
                               items={[
                                 {
                                   key: '1',
-                                  label: `查询到“${message.sliceInfo.doc.length}条”相关切片`,
+                                  // label: `查询到“${message.sliceInfo?.doc?.length}条”相关切片`,
+                                  label: `查询到${message.sliceInfo?.doc?.length}个文档共${message.sliceInfo?.allChunkNum}条切片`,
                                   children: <div>
-                                    {message.sliceInfo.doc.map((item, index) => {
+                                    {message.sliceInfo?.doc?.map((item, index) => {
                                       return <div
                                         style={{
                                           padding: 10,
@@ -2059,7 +2120,7 @@ function _Chat() {
                                         key={item.doc_id}
                                         onClick={() => {
                                           setDrawerData({
-                                            knowledge_id: message.sliceInfo!.knowledge_id,
+                                            knowledge_id: item!.knowledge_id,
                                             doc_name: item.doc_name,
                                             chunk_info: {
                                               doc_id: item.doc_id,

+ 60 - 37
app/components/home.tsx

@@ -342,12 +342,14 @@ export function Home() {
     }
   }
 
-  const frameLogin = async (data: { clientId: string,
+  const frameLogin = async (data: {
+    clientId: string,
     workspaceId: string,
     workspaceName: string,
     userName: string,
     timestamp: string,
-    signature: string }, url: string,fullUrl:string) => {
+    signature: string
+  }, url: string, fullUrl: string) => {
     try {
       const res = await api.post('frame_login', data);
       localStorage.setItem('userInfo', JSON.stringify(res.data));
@@ -359,13 +361,14 @@ export function Home() {
       });
       //停留5秒
       setTimeout(() => {
-        toUninLogin(url,fullUrl);
+        toUninLogin(url, fullUrl);
       }, 5000);
 
     }
   }
 
-  const toUninLogin = async (originUrl:string, fullUrl:string) => {
+  const toUninLogin = async (originUrl: string, fullUrl: string) => {
+    // return
     //测试环境
     //const loginUrl = 'https://esctest.sribs.com.cn/esc-sso/oauth2.0/authorize?client_id=e97f94cf93761f4d69e8&response_type=code';
     //生产环境
@@ -392,6 +395,25 @@ export function Home() {
     // if (loginRes) {
     //   return localStorage.setItem('userInfo', JSON.stringify(loginRes));
     // }
+    // 如果有token就先去存数据
+    const hash = window.location.hash;
+    const queryString = hash.includes("?") ? hash.split("?")[1] : "";
+    const urlHachParams = new URLSearchParams(queryString);
+    const token = urlHachParams.get('token');
+    const nickName = urlHachParams.get('nickName');
+    const userId = urlHachParams.get('userId');
+    if(token) {
+      localStorage.setItem('userInfo', JSON.stringify({ token, nickName, userId }));
+      // 清除url中的token参数
+      if (window.history.replaceState) {
+        const cleanUrl = window.location.href.split("#")[0];
+        // http://localhost:4000/#/knowledgeChat?showMenu=true&chatMode=LOCAL&appId=2965620717148049408&userId=8&nickName=test&token=eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjQ3M2QzOWI0LTVhOGYtNGYxZS1iOGY
+        window.history.replaceState({}, document.title, `${cleanUrl}#/knowledgeChat?showMenu=true&chatMode=LOCAL&&appId=${urlHachParams.get('appId')}`);
+      }
+    }
+
+
+
     const originUrl = window.location.origin;
     const fullUrl = window.location.href;
     const urlParams = new URLSearchParams(new URL(fullUrl).search);
@@ -399,47 +421,48 @@ export function Home() {
     const state = urlParams.get('state');
     const userInfo = localStorage.getItem('userInfo');
 
+
     if (fullUrl.includes(originUrl + '/?code') && code && state) {// 通过code登陆
       if (!userInfo) {
         jkLogin({ code: code, redirectUrl: encodeURIComponent(originUrl) }, state);
       }
     } else {
 
-        //判断是否是frame方式联登/frame
-        if (fullUrl.includes(originUrl + '/?frame=Y')) {
-
-          const workspaceId = urlParams.get('workspace_id');
-          const workspaceName = urlParams.get('workspace_name');
-          const username = urlParams.get('username');
-          const clientId = urlParams.get('client_id');
-          const timestamp = urlParams.get('timestamp');
-          const signature = urlParams.get('signature');
-          if (!clientId || !workspaceId || !workspaceName || !username || !timestamp || !signature) {
-            // 处理缺失参数的情况
-            Modal.error({
-              title: '参数错误',
-              content: '缺少必要的参数',
-            });
-            //停留5秒
-            setTimeout(() => {
-              toUninLogin(originUrl,fullUrl);
-            }, 5000);
-            return;
-          }
-          frameLogin({
-            clientId: clientId,
-            workspaceId: workspaceId,
-            workspaceName: workspaceName,
-            userName: username,
-            timestamp: timestamp,
-            signature: signature
-          },originUrl,fullUrl);
-
-        } else {
-          if (!userInfo) {
+      //判断是否是frame方式联登/frame
+      if (fullUrl.includes(originUrl + '/?frame=Y')) {
+
+        const workspaceId = urlParams.get('workspace_id');
+        const workspaceName = urlParams.get('workspace_name');
+        const username = urlParams.get('username');
+        const clientId = urlParams.get('client_id');
+        const timestamp = urlParams.get('timestamp');
+        const signature = urlParams.get('signature');
+        if (!clientId || !workspaceId || !workspaceName || !username || !timestamp || !signature) {
+          // 处理缺失参数的情况
+          Modal.error({
+            title: '参数错误',
+            content: '缺少必要的参数',
+          });
+          //停留5秒
+          setTimeout(() => {
             toUninLogin(originUrl, fullUrl);
-          }
+          }, 5000);
+          return;
         }
+        frameLogin({
+          clientId: clientId,
+          workspaceId: workspaceId,
+          workspaceName: workspaceName,
+          userName: username,
+          timestamp: timestamp,
+          signature: signature
+        }, originUrl, fullUrl);
+
+      } else {
+        if (!userInfo) {
+          toUninLogin(originUrl, fullUrl);
+        }
+      }
     }
   }, []);
 

+ 7 - 1
app/components/markdown.tsx

@@ -215,6 +215,7 @@ function _MarkDownContent(props: { content: string }) {
       components={{
         pre: PreCode,
         code: ({ className, children }) => {
+          // console.log('className, children-----3',className, children)
           if (className && className.includes('language-think')) {
             return (
               <code style={{ whiteSpace: 'pre-wrap', background: '#f3f4f6', color: '#525252' }}>
@@ -225,14 +226,19 @@ function _MarkDownContent(props: { content: string }) {
             return children;
           }
         },
-        p: (pProps) => <p {...pProps} dir="auto" />,
+        p: (pProps) => {
+          // console.log('pProps-----2',pProps)
+         return <p {...pProps} dir="auto" />
+        },
         a: (aProps) => {
+          // console.log('aProps-----4',aProps)
           const href = aProps.href || "";
           const isInternal = /^\/#/i.test(href);
           const target = isInternal ? "_self" : aProps.target ?? "_blank";
           return <a {...aProps} target={target} />;
         },
         img: ({ src, alt }) => (
+          // console.log('src, alt-----5',src, alt),
           <div style={{ width: '100%', height: 'auto', cursor: 'pointer' }}>
             <Image
               width='80%'

+ 7 - 0
app/components/sidebar.tsx

@@ -648,6 +648,13 @@ export const SideBar = (props: { className?: string }) => {
               }
               const res = await api.get(url);
               const list = res.data.map(((item: any) => {
+                if(item.sliceInfo){
+                  let allChunkNum = 0;
+                  item.sliceInfo.doc.forEach((doc: any) => {
+                    allChunkNum += doc.chunk_nums;
+                  });
+                  item.sliceInfo.allChunkNum = allChunkNum;
+                }
                 return {
                   id: item.did,
                   role: item.type,

+ 1 - 0
app/store/chat.ts

@@ -41,6 +41,7 @@ export type ChatMessage = RequestMessage & {
   sliceInfo?: {
     knowledge_id: string,
     doc: any[],
+    allChunkNum?: number,
   },
   networkInfo?: {
     list: any[],

+ 3 - 2
next.config.mjs

@@ -92,8 +92,9 @@ if (mode !== "export") {
       },
       {
         source: "/bigmodel-api/:path*",
-        destination: "http://192.168.3.3:8091/:path*",
-        // destination: "http://192.168.3.123:8091/:path*",
+        // destination: "http://192.168.3.3:8091/:path*",
+        // destination: "http://xia0miduo.gicp.net:8091/:path*",
+        destination: "http://192.168.3.123:8091/:path*",
       },
       {
         source: "/deepseek-api/:path*",