Bläddra i källkod

fix docs chat

tycoding 1 år sedan
förälder
incheckning
63793a365c

+ 1 - 1
langchat-common/src/main/java/cn/tycoding/langchat/common/utils/OssUtil.java

@@ -91,7 +91,7 @@ public class OssUtil {
                 .setPath(targetFile.getPath())
                 .setSize(originSize)
                 .setType(FileUtil.getSuffix(targetName))
-                .setUrl(getUrl(props.getRemotePath() + "/cdn", bucket, targetName))
+                .setUrl(getUrl(props.getRemotePath(), bucket, targetName))
                 .setCreateTime(new Date())
                 ;
     }

+ 0 - 2
langchat-core/src/main/java/cn/tycoding/langchat/core/service/Assistant.java

@@ -11,6 +11,4 @@ import dev.langchain4j.service.UserMessage;
 public interface Assistant {
 
     TokenStream chat(@UserMessage ChatMessage messages);
-
-    TokenStream chat(String query);
 }

+ 5 - 6
langchat-core/src/main/java/cn/tycoding/langchat/core/service/impl/LangDocServiceImpl.java

@@ -19,7 +19,7 @@ import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentPa
 import dev.langchain4j.data.document.splitter.DocumentSplitters;
 import dev.langchain4j.data.embedding.Embedding;
 import dev.langchain4j.data.segment.TextSegment;
-import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.chat.StreamingChatLanguageModel;
 import dev.langchain4j.model.embedding.EmbeddingModel;
 import dev.langchain4j.model.openai.OpenAiTokenizer;
 import dev.langchain4j.rag.content.retriever.ContentRetriever;
@@ -58,8 +58,7 @@ public class LangDocServiceImpl implements LangDocService {
     @Override
     public void embedDoc(OssR req) {
         EmbeddingModel model = provider.embed();
-        Document document = FileSystemDocumentLoader.loadDocument(req.getUrl(),
-                new ApachePdfBoxDocumentParser());
+        Document document = FileSystemDocumentLoader.loadDocument(req.getUrl(), new ApachePdfBoxDocumentParser());
         Map<String, Object> beanMap = BeanUtil.beanToMap(req);
         beanMap.forEach((k, v) -> {
             document.metadata().add(k, v);
@@ -77,7 +76,7 @@ public class LangDocServiceImpl implements LangDocService {
 
     @Override
     public TokenStream search(DocR req) {
-        ChatLanguageModel chatLanguageModel = modelProvider.text(ModelConst.OPENAI_TEXT);
+        StreamingChatLanguageModel chatLanguageModel = modelProvider.stream(ModelConst.OPENAI);
         EmbeddingModel model = provider.embed();
         Function<Query, Filter> filterByUserId = (query) -> metadataKey("id").isEqualTo(
                 req.getId());
@@ -89,10 +88,10 @@ public class LangDocServiceImpl implements LangDocService {
                 .build();
 
         Assistant assistant = AiServices.builder(Assistant.class)
-                .chatLanguageModel(chatLanguageModel)
+                .streamingChatLanguageModel(chatLanguageModel)
                 .contentRetriever(contentRetriever)
                 .build();
 
-        return assistant.chat("Which animal?");
+        return assistant.chat(req.getPrompt().toUserMessage());
     }
 }

+ 2 - 2
langchat-server/src/main/java/cn/tycoding/langchat/server/endpoint/DocsEndpoint.java

@@ -34,7 +34,7 @@ public class DocsEndpoint {
     public SseEmitter chat(@RequestBody DocR req) {
         StreamEmitter emitter = new StreamEmitter();
         req.setEmitter(emitter);
-        req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.CHART_LINE));
+        req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.DOCUMENT));
         chatService.docsChat(req);
         return emitter.get();
     }
@@ -44,7 +44,7 @@ public class DocsEndpoint {
         SysOss oss = ossService.upload(file);
         asyncFuture.async(() -> {
             chatService.docsEmbed(oss);
-        },"1","1");
+        }, System.currentTimeMillis() + "", oss.getId());
         return R.ok(oss);
     }
 }

+ 0 - 1
langchat-ui-client/.env

@@ -2,7 +2,6 @@
 VITE_GLOB_API_URL=/api
 
 VITE_APP_API_BASE_URL=http://127.0.0.1:8100/
-VITE_APP_CDN_BASE_URL=http://127.0.0.1:9000
 
 # Whether long replies are supported, which may result in higher API fees
 VITE_GLOB_OPEN_LONG_REPLY=false

+ 5 - 2
langchat-ui-client/src/api/docs.ts

@@ -1,5 +1,5 @@
 import { http } from '@/utils/request';
-import { ChatR, Oss } from '@/api/models';
+import { Oss } from '@/api/models';
 import { AxiosProgressEvent } from 'axios';
 
 /**
@@ -20,7 +20,10 @@ export function upload(data: any, onUploadProgress?: (progressEvent: AxiosProgre
  * @description: Doc聊天
  */
 export function chat(
-  data: ChatR,
+  data: {
+    id: string;
+    message: string;
+  },
   onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
 ) {
   return http.post({

+ 0 - 3
langchat-ui-client/src/main.ts

@@ -7,9 +7,6 @@ import { setupRouter } from './router';
 import { setupNaive } from '@/plugins/naive';
 import FileViewer from '@flyfish-group/file-viewer3';
 
-// 导入样式
-import '@flyfish-group/file-viewer3/dist/style.css';
-
 async function bootstrap() {
   const app = createApp(App);
   setupAssets();

+ 2 - 0
langchat-ui-client/src/plugins/naive.ts

@@ -83,6 +83,7 @@ import {
   NColorPicker,
   NEmpty,
   NPopselect,
+  NSplit,
 } from 'naive-ui';
 
 // https://www.naiveui.com/en-US/os-theme/docs/import-on-demand
@@ -170,6 +171,7 @@ const naive = create({
     NColorPicker,
     NEmpty,
     NPopselect,
+    NSplit,
   ],
 });
 

+ 0 - 1
langchat-ui-client/src/typings/env.d.ts

@@ -3,7 +3,6 @@
 interface ImportMetaEnv {
   readonly VITE_GLOB_API_URL: string;
   readonly VITE_APP_API_BASE_URL: string;
-  readonly VITE_APP_CDN_BASE_URL: string;
   readonly VITE_GLOB_OPEN_LONG_REPLY: string;
   readonly VITE_GLOB_APP_PWA: string;
   readonly VITE_WATER_MARK: string;

+ 34 - 10
langchat-ui-client/src/views/modules/doc/components/Chat.vue

@@ -10,8 +10,10 @@
   import Message from './Message.vue';
   import { SvgIcon } from '@/components/common';
 
+  const props = defineProps<{
+    file: any;
+  }>();
   const emits = defineEmits(['focus-active']);
-
   const messageRef = ref();
   const router = useRouter();
   const message = ref('');
@@ -43,6 +45,7 @@
     {
       id: string;
       inversion: boolean;
+      error: boolean;
       message: string;
       time?: number;
       usedToken?: number;
@@ -50,19 +53,25 @@
   >([]);
 
   async function handleSubmit() {
+    if (props.file.id === undefined) {
+      window.$message?.error('请先选择文档');
+      return;
+    }
+
     loading.value = true;
     messageRef.value.scrollToBottom();
-
     try {
       let id = uuid();
       messages.value.push(
         {
           id: uuid(),
+          error: false,
           inversion: false,
           message: message.value,
         },
         {
           id: id,
+          error: false,
           inversion: true,
           message: '',
           usedToken: 0,
@@ -72,6 +81,7 @@
       const items = messages.value.filter((i) => i.id == id);
       await chat(
         {
+          id: props.file?.id,
           message: message.value,
         },
         ({ event }) => {
@@ -87,14 +97,23 @@
               items[0].time = time;
             } else {
               text += message;
+              items[0].message = mdi.render(text);
+              messageRef.value.scrollToBottom();
             }
           });
-          items[0].message = mdi.render(text);
-          messageRef.value.scrollToBottom();
         }
-      ).catch(() => {});
-      message.value = '';
-      loading.value = false;
+      )
+        .catch((err: any) => {
+          if (err.message !== undefined) {
+            items[0].error = true;
+            items[0].message = err.message;
+          }
+          loading.value = false;
+        })
+        .finally(() => {
+          message.value = '';
+          loading.value = false;
+        });
     } finally {
       loading.value = false;
       messageRef.value.scrollToBottom();
@@ -114,8 +133,8 @@
   <div class="container relative h-full card-shadow rounded-xl mb-2">
     <Message ref="messageRef" :messages="messages" />
 
-    <div class="bottom absolute bottom-2 left-0 w-full h-[60px] z-10">
-      <div class="pl-12 pr-12 flex justify-center items-center space-x-2 w-full">
+    <div class="bottom absolute bottom-2 pt-5 left-0 w-full h-[60px] z-10">
+      <div class="px-8 flex justify-center items-center space-x-2 w-full">
         <n-input
           v-model:value="message"
           :autosize="{ minRows: 1, maxRows: 5 }"
@@ -140,4 +159,9 @@
   </div>
 </template>
 
-<style lang="less"></style>
+<style scoped lang="less">
+  ::v-deep(.markdown-body) {
+    background-color: transparent !important;
+    font-size: inherit;
+  }
+</style>

+ 2 - 1
langchat-ui-client/src/views/modules/doc/components/FileList.vue

@@ -9,7 +9,7 @@
   const props = defineProps<{
     file: any;
   }>();
-  const emit = defineEmits(['select']);
+  const emit = defineEmits(['select', 'clear']);
   const message = useMessage();
   const fileList = ref<Oss[]>([]);
   const loading = ref(true);
@@ -58,6 +58,7 @@
     await del(item.id);
     message.success(t('common.deleteSuccess'));
     await fetchData();
+    emit('clear');
   }
 
   async function onUpdate(item: Oss) {

+ 46 - 0
langchat-ui-client/src/views/modules/doc/components/FileView.vue

@@ -0,0 +1,46 @@
+<script setup lang="ts">
+  import '@flyfish-group/file-viewer3/dist/style.css';
+  defineProps({
+    url: {
+      type: String,
+    },
+  });
+</script>
+
+<template>
+  <file-viewer :url="url" />
+</template>
+
+<style scoped lang="less">
+  ::v-deep(.name) {
+    display: none !important;
+  }
+  ::v-deep(.docx) {
+    color: #343639;
+    background: #f2f2f2 !important;
+  }
+  ::v-deep(.docx span) {
+    font-size: medium !important;
+  }
+  ::v-deep(.docx-wrapper) {
+    padding: 0 !important;
+    .docx {
+      width: auto !important;
+      padding: 5pt 15pt !important;
+      //background: #f2f2f2 !important;
+    }
+  }
+  ::v-deep(.code-area) {
+    width: auto !important;
+    background: none !important;
+    color: #101014 !important;
+    padding: 10px 17px;
+    font-size: medium;
+    font-family: normal !important;
+    padding-bottom: 30px;
+  }
+  ::v-deep(.code-area:is(.dark *)) {
+    background: #101014 !important;
+    color: white !important;
+  }
+</style>

+ 4 - 2
langchat-ui-client/src/views/modules/doc/components/Message.vue

@@ -8,6 +8,7 @@
   interface Props {
     messages: {
       inversion: boolean;
+      error: boolean;
       message: string;
       time?: number;
       usedToken?: number;
@@ -85,7 +86,7 @@
       </div>
     </div>
     <div v-else class="flex-1 overflow-y-auto mb-1">
-      <div class="h-full w-full flex flex-col space-y-3 relative p-3 pt-0">
+      <div class="h-full w-full flex flex-col space-y-3 relative px-5 pt-0">
         <template v-for="item in messages" :key="item">
           <div
             v-if="!item.inversion"
@@ -101,7 +102,8 @@
               <span class="dot-loading"></span>
             </div>
             <div v-else class="p-2 pl-3 pr-3">
-              <div class="pb-2 markdown-body" v-html="item.message"></div>
+              <div v-if="item.error" class="text-red-400" v-text="item.message"></div>
+              <div v-else class="markdown-body pb-2" v-html="item.message"></div>
               <div
                 v-if="item.time !== 0"
                 class="border-t border-gray-200 pt-2 text-xs text-gray-400 flex flex-row justify-between items-center min-w-[200px]"

+ 32 - 27
langchat-ui-client/src/views/modules/doc/index.vue

@@ -5,6 +5,7 @@
   import { useMessage } from 'naive-ui';
   import { Oss } from '@/api/models';
   import { t } from '@/locales';
+  import FileView from './components/FileView.vue';
 
   const message = useMessage();
   const file = ref<Oss>({});
@@ -15,6 +16,10 @@
     }
     file.value = item;
   }
+
+  function onClear() {
+    file.value.url = '';
+  }
 </script>
 
 <template>
@@ -26,35 +31,35 @@
       show-trigger="arrow-circle"
       bordered
     >
-      <FileList :file="file" @select="onSelect" />
+      <FileList :file="file" @clear="onClear" @select="onSelect" />
     </n-layout-sider>
-    <div class="w-full h-full flex flex-row pt-2 gap-2">
-      <div class="w-8/12 h-full overflow-y-auto">
-        <div
-          v-if="file.fileName"
-          class="text-gray-700 text-[17px] pl-2 pb-2 pt-0 font-bold h-min-8 mt-0.5"
-        >
-          {{ file.fileName }}.{{ file.type }}
-        </div>
-        <n-empty
-          v-if="file.url === undefined"
-          class="h-full w-full justify-center"
-          :description="t('doc.previewEmpty')"
-        />
-        <template v-else>
-          <file-viewer :url="file.url" />
-          <!--          <iframe v-if="file.type === 'pdf'" width="100%" height="100%" :src="file.url"></iframe>-->
-          <!--          <iframe-->
-          <!--            v-else-->
-          <!--            width="100%"-->
-          <!--            height="100%"-->
-          <!--            :src="'https://view.officeapps.live.com/op/embed.aspx?src=' + file.url"-->
-          <!--          ></iframe>-->
+    <div class="w-full h-full">
+      <n-split direction="horizontal" class="h-full" :default-size="0.6">
+        <template #1>
+          <div class="w-full h-full">
+            <div
+              v-if="file.fileName"
+              class="text-gray-700 text-[17px] border-b px-4 font-bold h-12 flex justify-between items-center dark:text-white"
+            >
+              <div>{{ file.fileName }}.{{ file.type }}</div>
+              <div>OpenAI</div>
+            </div>
+            <n-empty
+              v-if="file.url === undefined"
+              class="h-full w-full justify-center"
+              :description="t('doc.previewEmpty')"
+            />
+            <template v-else>
+              <FileView :url="file.url" />
+            </template>
+          </div>
+        </template>
+        <template #2>
+          <div class="w-full h-full border-l dark:border-l-[#1e1e20]">
+            <Chat :file="file" />
+          </div>
         </template>
-      </div>
-      <div class="w-4/12 border-l dark:border-l-[#1e1e20]">
-        <Chat />
-      </div>
+      </n-split>
     </div>
   </n-layout>
 </template>

+ 0 - 5
langchat-ui-client/vite.config.ts

@@ -42,11 +42,6 @@ export default defineConfig((env) => {
           changeOrigin: true, // 允许跨域
           rewrite: (path) => path.replace('/api/', '/'),
         },
-        '/cdn': {
-          target: viteEnv.VITE_APP_CDN_BASE_URL,
-          changeOrigin: true, // 允许跨域
-          rewrite: (path) => path.replace('/cdn/', '/'),
-        },
       },
     },
     build: {