浏览代码

上传文件

李富豪 7 月之前
父节点
当前提交
f2961473f5

+ 4 - 0
app/client/api.ts

@@ -34,6 +34,10 @@ export interface MultimodalContent {
 export interface RequestMessage {
   role: MessageRole;
   content: string | MultimodalContent[];
+  document?: {
+    id: string,
+    url: string,
+  }
 }
 
 export interface LLMConfig {

+ 6 - 8
app/client/platforms/bigModel.ts

@@ -20,15 +20,13 @@ export class BigModelApi implements LLMApi {
   public apiPath: string;
 
   constructor() {
-    const status = useChatStore.getState().deepSeekStatus;
+    const chatMode = useChatStore.getState().chatMode;
     this.baseURL = '/bigmodel-api';
-    if(status === 'LOCAL'){
+    if (chatMode === 'LOCAL') {
       this.apiPath = this.baseURL + '/bigmodel/api/model-api/sse-invoke';
-    }else{
+    } else {
       this.apiPath = this.baseURL + '/takai/api/chat';
     }
-    // 本地流式调用
-    // this.apiPath = '  http://xia0miduo.gicp.net:8091' + '/bigmodel/api/model-api/sse-invoke';
   }
 
   async chat(options: ChatOptions) {
@@ -184,10 +182,10 @@ export class BigModelApi implements LLMApi {
               }
             });
           }
-          const status = useChatStore.getState().deepSeekStatus;
-          if(status === 'LOCAL'){
+          const chatMode = useChatStore.getState().chatMode;
+          if (chatMode === 'LOCAL') {
             await api.post('bigmodel/api/dialog/save', data);
-          }else{
+          } else {
             await api.post('takai/api/dialog/save', data);
           }
         },

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

@@ -16,13 +16,30 @@ import { getMessageTextContent } from "@/app/utils";
 import api from "@/app/api/api";
 
 export class DeepSeekApi implements LLMApi {
+  public baseURL: string;
   public apiPath: string;
 
   constructor() {
-    this.apiPath = 'http://sse.deepseek.ryuiso.com:56780/chat';
+    this.baseURL = '/deepseek-api';
+    this.apiPath = this.baseURL + '/vllm/ai/chat';
   }
 
   async chat(options: ChatOptions) {
+    const list: ChatOptions['messages'] = JSON.parse(JSON.stringify(options.messages)) || [];
+
+    const backList = list.reverse();
+    const item = backList.find((item) => {
+      if (item.document) {
+        if (item.document.id) {
+          return true;
+        } else {
+          return false;
+        }
+      } else {
+        return false;
+      }
+    });
+
     const messages = options.messages.map((item) => {
       return {
         role: item.role,
@@ -41,9 +58,10 @@ export class DeepSeekApi implements LLMApi {
 
     // 参数
     const params = {
-      model: 'deepseek-r1:8b',
+      model: 'DeepSeek-R1-Distill-Qwen-14B',
       messages: userMessages,
       stream: true,
+      document_id: (item && item.document) ? item.document.id : undefined,
       // 进阶配置
       max_tokens: undefined,
       temperature: undefined,

+ 88 - 19
app/components/DeepSeekChat.tsx

@@ -83,6 +83,8 @@ import { ExportMessageModal } from "./exporter";
 import { getClientConfig } from "../config/client";
 import { useAllModels } from "../utils/hooks";
 import { nanoid } from "nanoid";
+import { message, Upload, UploadProps } from "antd";
+import { PaperClipOutlined, SendOutlined } from '@ant-design/icons';
 
 export function createMessage(override: Partial<ChatMessage>): ChatMessage {
   return {
@@ -864,7 +866,7 @@ function _Chat() {
     }
   };
 
-  const doSubmit = (userInput: string) => {
+  const doSubmit = (userInput: string, fileList?: any[]) => {
     if (userInput.trim() === "") return;
     const matchCommand = chatCommands.match(userInput);
     if (matchCommand.matched) {
@@ -874,12 +876,13 @@ function _Chat() {
       return;
     }
     setIsLoading(true);
-    chatStore.onUserInput(userInput, attachImages).then(() => setIsLoading(false));
+    chatStore.onUserInput(fileList || [], userInput, attachImages).then(() => setIsLoading(false));
     setAttachImages([]);
     localStorage.setItem(LAST_INPUT_KEY, userInput);
     setUserInput("");
     setPromptHints([]);
     if (!isMobileScreen) inputRef.current?.focus();
+    setFileList([]);
     setAutoScroll(true);
   };
 
@@ -1025,7 +1028,7 @@ function _Chat() {
     setIsLoading(true);
     const textContent = getMessageTextContent(userMessage);
     const images = getMessageImages(userMessage);
-    chatStore.onUserInput(textContent, images).then(() => setIsLoading(false));
+    chatStore.onUserInput([], textContent, images).then(() => setIsLoading(false));
     inputRef.current?.focus();
   };
 
@@ -1303,6 +1306,15 @@ function _Chat() {
     setAttachImages(images);
   }
 
+  const [fileList, setFileList] = useState<any[]>([]);
+
+  // 上传配置
+  const uploadConfig: UploadProps = {
+    action: '/deepseek-api' + '/upload/file',
+    method: 'POST',
+    accept: ['.pdf', '.txt', '.doc', '.docx'].join(','),
+  };
+
   return (
     <div className={styles.chat} key={session.id}>
       {
@@ -1443,6 +1455,17 @@ function _Chat() {
             onSearch("");
           }}
         />
+        {
+          fileList.length > 0 &&
+          <div style={{ marginBottom: 20 }}>
+            <Upload
+              fileList={fileList}
+              onRemove={(file) => {
+                setFileList(fileList.filter(item => item.uid !== file.uid));
+              }}
+            />
+          </div>
+        }
         <label
           className={`${styles["chat-input-panel-inner"]} ${attachImages.length != 0
             ? styles["chat-input-panel-inner-attach"]
@@ -1511,28 +1534,74 @@ function _Chat() {
               </div>
             </div>
           </div>
-          <div
-            style={{ width: 35, height: 35, borderRadius: '50%', background: '#4357d2', display: 'flex', justifyContent: 'center', alignItems: 'center' }}
-            onClick={() => doSubmit(userInput)}
-          >
-            <SendWhiteIcon />
+          <div style={{ display: 'flex', alignItems: 'center' }}>
+            <div style={{ marginRight: 20 }}>
+              <Upload
+                {...uploadConfig}
+                showUploadList={false}
+                maxCount={1}
+                onChange={(info) => {
+                  const fileList = info.fileList.map((file) => {
+                    const data = file.response;
+                    return {
+                      ...file,
+                      url: data?.document_url || file.url,
+                      documentId: data?.document_id || '',
+                    }
+                  });
+                  setFileList(fileList);
+                  if (info.file.status === 'done') {// 上传成功
+                    const { code, message: msg } = info.file.response;
+                    if (code === 200) {
+                      message.success('上传成功');
+                    } else {
+                      message.error(msg);
+                    }
+                  } else if (info.file.status === 'error') {// 上传失败
+                    message.error('上传失败');
+                  }
+                }}
+              >
+                <div
+                  style={{
+                    width: 35, height: 35, borderRadius: '50%', background: '#4357d2', display: 'flex', justifyContent: 'center', alignItems: 'center', cursor: 'pointer'
+                  }}
+                >
+                  <PaperClipOutlined style={{ color: '#FFFFFF', fontSize: '18px' }} />
+                </div>
+              </Upload>
+            </div>
+            <div
+              style={{
+                width: 35, height: 35, borderRadius: '50%', background: '#4357d2', display: 'flex', justifyContent: 'center', alignItems: 'center', cursor: 'pointer'
+              }}
+              onClick={() => doSubmit(userInput, fileList)}
+            >
+              <div style={{ transform: 'rotate(-45deg)', padding: '0px 0px 3px 5px' }}>
+                <SendOutlined style={{ color: '#FFFFFF' }} />
+              </div>
+            </div>
           </div>
         </div>
         <div style={{ marginTop: 10, textAlign: 'center', color: '#888888', fontSize: 12 }}>
           内容由AI生成,仅供参考
         </div>
       </div>
-      {showExport && (
-        <ExportMessageModal onClose={() => setShowExport(false)} />
-      )}
-      {isEditingMessage && (
-        <EditMessageModal
-          onClose={() => {
-            setIsEditingMessage(false);
-          }}
-        />
-      )}
-    </div>
+      {
+        showExport && (
+          <ExportMessageModal onClose={() => setShowExport(false)} />
+        )
+      }
+      {
+        isEditingMessage && (
+          <EditMessageModal
+            onClose={() => {
+              setIsEditingMessage(false);
+            }}
+          />
+        )
+      }
+    </div >
   );
 }
 

+ 9 - 4
app/components/DeepSeekHomeChat.tsx

@@ -83,6 +83,7 @@ import { ExportMessageModal } from "./exporter";
 import { getClientConfig } from "../config/client";
 import { useAllModels } from "../utils/hooks";
 import { nanoid } from "nanoid";
+import { SendOutlined } from '@ant-design/icons';
 
 export function createMessage(override: Partial<ChatMessage>): ChatMessage {
   return {
@@ -868,7 +869,7 @@ function _Chat() {
       return;
     }
     setIsLoading(true);
-    chatStore.onUserInput(userInput, attachImages).then(() => setIsLoading(false));
+    chatStore.onUserInput([], userInput, attachImages).then(() => setIsLoading(false));
     setAttachImages([]);
     localStorage.setItem(LAST_INPUT_KEY, userInput);
     setUserInput("");
@@ -1019,7 +1020,7 @@ function _Chat() {
     setIsLoading(true);
     const textContent = getMessageTextContent(userMessage);
     const images = getMessageImages(userMessage);
-    chatStore.onUserInput(textContent, images).then(() => setIsLoading(false));
+    chatStore.onUserInput([], textContent, images).then(() => setIsLoading(false));
     inputRef.current?.focus();
   };
 
@@ -1490,10 +1491,14 @@ function _Chat() {
             </div>
           </div>
           <div
-            style={{ width: 35, height: 35, borderRadius: '50%', background: '#4357d2', display: 'flex', justifyContent: 'center', alignItems: 'center' }}
+            style={{
+              width: 35, height: 35, borderRadius: '50%', background: '#4357d2', display: 'flex', justifyContent: 'center', alignItems: 'center', cursor: 'pointer'
+            }}
             onClick={() => doSubmit(userInput)}
           >
-            <SendWhiteIcon />
+            <div style={{ transform: 'rotate(-45deg)', padding: '0px 0px 3px 5px' }}>
+              <SendOutlined style={{ color: '#FFFFFF' }} />
+            </div>
           </div>
         </div>
         <div style={{ marginTop: 10, textAlign: 'center', color: '#888888', fontSize: 12 }}>

+ 7 - 7
app/components/chat.tsx

@@ -621,7 +621,7 @@ export function ChatActions(props: {
         ]
       }
       let url = '';
-      if (chatStore.deepSeekStatus === 'LOCAL') {
+      if (chatStore.chatMode === 'LOCAL') {
         url = '/bigmodel/api/async/completions';
       } else {
         url = '/takai/api/async/completions';
@@ -997,7 +997,7 @@ function _Chat() {
     { id: 'LOCAL', name: '本地' },
   ];
 
-  const [selectedFruit, setSelectedFruit] = React.useState(chatStore.deepSeekStatus); // 默认选中
+  const [selectedFruit, setSelectedFruit] = React.useState(chatStore.chatMode);
 
   useEffect(() => {
   }, []);
@@ -1076,11 +1076,11 @@ function _Chat() {
     if (appValue) {
       fetchDefaultQuestion(appValue);
     }
-  }, [appValue, chatStore.deepSeekStatus])
+  }, [appValue, chatStore.chatMode])
 
   useEffect(() => {
     fetchApplicationList();
-  }, [chatStore.deepSeekStatus])
+  }, [chatStore.chatMode])
 
   const [inputRows, setInputRows] = useState(2);
   const measure = useDebouncedCallback(
@@ -1145,7 +1145,7 @@ function _Chat() {
       return;
     }
     setIsLoading(true);
-    chatStore.onUserInput(userInput, attachImages).then(() => setIsLoading(false));
+    chatStore.onUserInput([], userInput, attachImages).then(() => setIsLoading(false));
     setAttachImages([]);
     localStorage.setItem(LAST_INPUT_KEY, userInput);
     setUserInput("");
@@ -1297,7 +1297,7 @@ function _Chat() {
     setIsLoading(true);
     const textContent = getMessageTextContent(userMessage);
     const images = getMessageImages(userMessage);
-    chatStore.onUserInput(textContent, images).then(() => setIsLoading(false));
+    chatStore.onUserInput([], textContent, images).then(() => setIsLoading(false));
     inputRef.current?.focus();
   };
 
@@ -1641,7 +1641,7 @@ function _Chat() {
             value={selectedFruit}
             onChange={(value: "ONLINE" | "LOCAL") => {
               setSelectedFruit(value);
-              chatStore.setDeepSeekStatus(value);
+              chatStore.setChatMode(value);
             }}
           >
             {fruits.map(fruit => (

+ 27 - 25
app/components/sidebar.tsx

@@ -216,23 +216,25 @@ export const SideBar = (props: { className?: string }) => {
   }
 
   // 获取聊天列表
-  const fetchChatList = async (deepSeekStatus?:'ONLINE' | 'LOCAL') => {
+  const fetchChatList = async (chatMode?: 'ONLINE' | 'LOCAL') => {
     try {
-      let appId = '';
-      if (getType() === 'bigModel') {
-        appId = globalStore.selectedAppId;
-      } else {
-        appId = '1881269958412521255';
-      }
-
       let url = '';
-      if (deepSeekStatus) {
-        if (deepSeekStatus==='LOCAL') {
+      if (getType() === 'bigModel') {
+        const appId = globalStore.selectedAppId;
+        if (chatMode) {
+          if (chatMode === 'LOCAL') {
+            url = `/bigmodel/api/dialog/list/${appId}`;
+          } else {
+            url = `/takai/api/dialog/list/${appId}`;
+          }
+        } else {
           url = `/bigmodel/api/dialog/list/${appId}`;
-        }else{
-          url = `/takai/api/dialog/list/${appId}`;
         }
+      } else {
+        const appId = '1881269958412521255';
+        url = `/bigmodel/api/dialog/list/${appId}`;
       }
+
       const res = await api.get(url);
       const list = res.data.map((item: any) => {
         return {
@@ -274,12 +276,12 @@ export const SideBar = (props: { className?: string }) => {
                 label: (
                   <a onClick={async () => {
                     try {
-                      if(deepSeekStatus === 'LOCAL'){
+                      if (chatMode === 'LOCAL') {
                         await api.delete(`/bigmodel/api/dialog/del/${child.key}`);
                         await fetchChatList();
-                      }else{
+                      } else {
                         await api.delete(`/takai/api/dialog/del/${child.key}`);
-                        await fetchChatList(deepSeekStatus);
+                        await fetchChatList(chatMode);
                       }
                       chatStore.clearSessions();
                       useChatStore.setState({
@@ -334,8 +336,8 @@ export const SideBar = (props: { className?: string }) => {
   }, []);
 
   useEffect(() => {
-    fetchChatList(chatStore.deepSeekStatus);
-  }, [chatStore.deepSeekStatus]);
+    fetchChatList(chatStore.chatMode);
+  }, [chatStore.chatMode]);
 
   return (
     <SideBarContainer
@@ -375,10 +377,10 @@ export const SideBar = (props: { className?: string }) => {
               } else {
                 navigate({ pathname: '/newDeepseekChat' });
               }
-              if(chatStore.deepSeekStatus === 'LOCAL'){
+              if (chatStore.chatMode === 'LOCAL') {
                 await fetchChatList();
-              }else{
-                await fetchChatList(chatStore.deepSeekStatus);
+              } else {
+                await fetchChatList(chatStore.chatMode);
               }
             }}
           >
@@ -390,9 +392,9 @@ export const SideBar = (props: { className?: string }) => {
         style={{ border: 'none' }}
         onClick={async ({ key }) => {
           let url = ``;
-          if(chatStore.deepSeekStatus === 'LOCAL'){
+          if (chatStore.chatMode === 'LOCAL') {
             url = `/bigmodel/api/dialog/detail/${key}`;
-          }else{
+          } else {
             url = `/takai/api/dialog/detail/${key}`;
           }
           const res = await api.get(url);
@@ -436,18 +438,18 @@ export const SideBar = (props: { className?: string }) => {
           form.validateFields().then(async (values) => {
             setModalOpen(false);
             try {
-              if(chatStore.deepSeekStatus === 'LOCAL'){
+              if (chatStore.chatMode === 'LOCAL') {
                 await api.put(`/bigmodel/api/dialog/update`, {
                   id: values.dialogId,
                   dialogName: values.dialogName
                 });
                 await fetchChatList();
-              }else{
+              } else {
                 await api.put(`/takai/api/dialog/update`, {
                   id: values.dialogId,
                   dialogName: values.dialogName
                 });
-                await fetchChatList(chatStore.deepSeekStatus);
+                await fetchChatList(chatStore.chatMode);
               }
               chatStore.updateCurrentSession((value) => {
                 value.topic = values.dialogName;

+ 20 - 4
app/store/chat.ts

@@ -35,6 +35,10 @@ export type ChatMessage = RequestMessage & {
   isError?: boolean;
   id: string;
   model?: ModelType;
+  document?: {
+    id: string,
+    url: string,
+  },
 };
 
 export function createMessage(override: Partial<ChatMessage>): ChatMessage {
@@ -167,7 +171,7 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) {
 
 const DEFAULT_CHAT_STATE = {
   model: 'BigModel' as 'BigModel' | 'DeepSeek',
-  deepSeekStatus: 'ONLINE' as 'ONLINE' | 'LOCAL',
+  chatMode: 'ONLINE' as 'ONLINE' | 'LOCAL',
   sessions: [createEmptySession()],
   currentSessionIndex: 0,
   message: {
@@ -189,8 +193,8 @@ export const useChatStore = createPersistStore(
       setModel(model: 'BigModel' | 'DeepSeek') {
         set({ model: model });
       },
-      setDeepSeekStatus(status: 'ONLINE' | 'LOCAL') {
-        set({ deepSeekStatus: status });
+      setChatMode(mode: 'ONLINE' | 'LOCAL') {
+        set({ chatMode: mode });
       },
       clearSessions() {
         set(() => ({
@@ -326,7 +330,7 @@ export const useChatStore = createPersistStore(
         get().summarizeSession();
       },
 
-      async onUserInput(content: string, attachImages?: string[]) {
+      async onUserInput(fileList: any[], content: string, attachImages?: string[]) {
         const session = get().currentSession();
         const modelConfig = session.mask.modelConfig;
 
@@ -353,9 +357,21 @@ export const useChatStore = createPersistStore(
             }),
           );
         }
+
+        const document = {
+          id: '',
+          url: '',
+        }
+
+        if (fileList.length) {
+          document.id = fileList[0].documentId;
+          document.url = fileList[0].url;
+        }
+
         let userMessage: ChatMessage = createMessage({
           role: "user",
           content: mContent,
+          document: document,
         });
 
         const botMessage: ChatMessage = createMessage({

+ 4 - 0
next.config.mjs

@@ -94,6 +94,10 @@ if (mode !== "export") {
         source: "/bigmodel-api/:path*",
         destination: "http://xia0miduo.gicp.net:8091/:path*",
       },
+      {
+        source: "/deepseek-api/:path*",
+        destination: "http://192.168.3.209:80/:path*",
+      },
     ];
 
     return {